Implement --target-host

This commit is contained in:
R-VdP 2023-02-20 20:19:54 +01:00
parent 49f30480ea
commit 3280073a0f
No known key found for this signature in database
4 changed files with 161 additions and 42 deletions

View file

@ -88,22 +88,39 @@ in
text = lib.generators.toJSON { } etcFiles;
};
registerProfileScript = pkgs.writeShellScript "register-profile" ''
${system-manager}/bin/system-manager generate \
--store-path "$(dirname $(realpath $(dirname ''${0})))" \
"$@"
'';
activationScript = pkgs.writeShellScript "activate" ''
${system-manager}/bin/system-manager activate \
--store-path "$(realpath $(dirname ''${0}))" \
--store-path "$(dirname $(realpath $(dirname ''${0})))" \
"$@"
'';
deactivationScript = pkgs.writeShellScript "deactivate" ''
${system-manager}/bin/system-manager deactivate "$@"
'';
linkFarmEntryFromDrv = drv: {
name = drv.name;
path = drv;
};
linkFarmBinEntryFromDrv = drv: {
name = "bin/${drv.name}";
path = drv;
};
in
returnIfNoAssertions (
pkgs.linkFarmFromDrvs "system-manager" [
servicesPath
etcPath
activationScript
deactivationScript
pkgs.linkFarm "system-manager" [
(linkFarmEntryFromDrv servicesPath)
(linkFarmEntryFromDrv etcPath)
(linkFarmBinEntryFromDrv activationScript)
(linkFarmBinEntryFromDrv deactivationScript)
(linkFarmBinEntryFromDrv registerProfileScript)
]
);
}

View file

@ -5,17 +5,18 @@ use anyhow::Result;
use crate::StorePath;
pub fn activate(store_path: StorePath, ephemeral: bool) -> Result<()> {
pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
log::info!("Activating system-manager profile: {store_path}");
if ephemeral {
log::info!("Running in ephemeral mode");
}
etc_files::activate(&store_path, ephemeral)?;
services::activate(&store_path, ephemeral)?;
etc_files::activate(store_path, ephemeral)?;
services::activate(store_path, ephemeral)?;
Ok(())
}
// TODO should we also remove the GC root for the profile if it exists?
pub fn deactivate() -> Result<()> {
log::info!("Deactivating system-manager");
etc_files::deactivate()?;

View file

@ -14,28 +14,18 @@ struct NixBuildOutput {
outputs: HashMap<String, String>,
}
pub fn generate(flake_uri: &str) -> Result<StorePath> {
let store_path = build(flake_uri)?;
pub fn generate(store_path: &StorePath) -> Result<()> {
let profile_dir = Path::new(PROFILE_DIR);
let profile_name = Path::new(PROFILE_NAME);
log::info!("Creating new generation from {store_path}");
install_nix_profile(&store_path, profile_dir, profile_name)?;
install_nix_profile(store_path, profile_dir, profile_name)?;
log::info!("Registering GC root...");
create_gcroot(GCROOT_PATH, &profile_dir.join(profile_name))?;
log::info!("Done");
Ok(store_path)
}
pub fn build(flake_uri: &str) -> Result<StorePath> {
// FIXME: we should not hard-code the system here
let flake_attr = format!("{FLAKE_ATTR}.x86_64-linux");
log::info!("Building new system-manager generation...");
log::info!("Running nix build...");
run_nix_build(flake_uri, &flake_attr).and_then(get_store_path)
Ok(())
}
fn install_nix_profile(
@ -61,6 +51,15 @@ fn create_gcroot(gcroot_path: &str, profile_path: &Path) -> Result<()> {
create_store_link(&store_path, Path::new(gcroot_path))
}
pub fn build(flake_uri: &str) -> Result<StorePath> {
// FIXME: we should not hard-code the system here
let flake_attr = format!("{FLAKE_ATTR}.x86_64-linux");
log::info!("Building new system-manager generation...");
log::info!("Running nix build...");
run_nix_build(flake_uri, &flake_attr).and_then(get_store_path)
}
fn get_store_path(nix_build_result: process::Output) -> Result<StorePath> {
if nix_build_result.status.success() {
String::from_utf8(nix_build_result.stdout)

View file

@ -1,4 +1,4 @@
use std::process::ExitCode;
use std::process::{self, ExitCode, ExitStatus};
use anyhow::Result;
use clap::Parser;
@ -10,6 +10,13 @@ use system_manager::StorePath;
struct Args {
#[command(subcommand)]
action: Action,
#[arg(long)]
/// The host to deploy the system-manager profile to
target_host: Option<String>,
#[arg(long, action)]
use_remote_sudo: bool,
}
#[derive(clap::Args, Debug)]
@ -19,6 +26,17 @@ struct BuildArgs {
flake_uri: String,
}
#[derive(clap::Args, Debug)]
struct GenerateArgs {
#[arg(long)]
/// The flake defining the system-manager profile
flake_uri: Option<String>,
#[arg(long)]
/// The store path containing the system-manager profile
store_path: Option<StorePath>,
}
#[derive(clap::Args, Debug)]
struct ActivationArgs {
#[arg(long, action)]
@ -45,7 +63,7 @@ enum Action {
/// Generate a new system-manager generation
Generate {
#[command(flatten)]
build_args: BuildArgs,
generate_args: GenerateArgs,
},
/// Generate a new system-manager generation and activate it
Switch {
@ -57,42 +75,69 @@ enum Action {
}
fn main() -> ExitCode {
let args = Args::parse();
// FIXME: set default level to info
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
handle_toplevel_error(go(args.action))
handle_toplevel_error(go(Args::parse()))
}
fn go(action: Action) -> Result<()> {
fn go(args: Args) -> Result<()> {
let Args {
action,
target_host,
use_remote_sudo,
} = args;
match action {
Action::Activate {
store_path,
activation_args: ActivationArgs { ephemeral },
} => {
check_root()?;
activate(store_path, ephemeral)
// FIXME handle target_host
copy_closure(&store_path, &target_host)?;
activate(&store_path, ephemeral, &target_host, use_remote_sudo)
}
Action::Build {
build_args: BuildArgs { flake_uri },
} => build(flake_uri),
Action::Deactivate => {
check_root()?;
// FIXME handle target_host
deactivate()
}
Action::Generate {
build_args: BuildArgs { flake_uri },
} => {
check_root()?;
generate(flake_uri).map(|_| ())
Action::Generate { generate_args } => {
generate(generate_args, &target_host, use_remote_sudo)
}
Action::Switch {
build_args: BuildArgs { flake_uri },
activation_args: ActivationArgs { ephemeral },
} => {
check_root()?;
let store_path = generate(flake_uri)?;
activate(store_path, ephemeral)
let store_path = do_build(flake_uri)?;
copy_closure(&store_path, &target_host)?;
do_generate(&store_path, &target_host, use_remote_sudo)?;
activate(&store_path, ephemeral, &target_host, use_remote_sudo)
}
}
}
fn generate(args: GenerateArgs, target_host: &Option<String>, use_remote_sudo: bool) -> Result<()> {
match args {
GenerateArgs {
flake_uri: Some(flake_uri),
store_path: None,
} => {
let store_path = do_build(flake_uri)?;
copy_closure(&store_path, target_host)?;
do_generate(&store_path, target_host, use_remote_sudo)
}
GenerateArgs {
flake_uri: None,
store_path: Some(store_path),
} => {
copy_closure(&store_path, target_host)?;
do_generate(&store_path, target_host, use_remote_sudo)
}
_ => {
anyhow::bail!("Supply either a flake URI or a store path.")
}
}
}
@ -109,18 +154,75 @@ fn do_build(flake_uri: String) -> Result<StorePath> {
system_manager::generate::build(&flake_uri)
}
fn generate(flake_uri: String) -> Result<StorePath> {
system_manager::generate::generate(&flake_uri)
fn do_generate(
store_path: &StorePath,
target_host: &Option<String>,
use_remote_sudo: bool,
) -> Result<()> {
if let Some(target_host) = target_host {
invoke_remote_script(store_path, "register-profile", target_host, use_remote_sudo)?;
Ok(())
} else {
check_root()?;
system_manager::generate::generate(store_path)
}
}
fn activate(store_path: StorePath, ephemeral: bool) -> Result<()> {
system_manager::activate::activate(store_path, ephemeral)
fn activate(
store_path: &StorePath,
ephemeral: bool,
target_host: &Option<String>,
use_remote_sudo: bool,
) -> Result<()> {
if let Some(target_host) = target_host {
invoke_remote_script(store_path, "activate", target_host, use_remote_sudo)?;
Ok(())
} else {
check_root()?;
system_manager::activate::activate(store_path, ephemeral)
}
}
fn deactivate() -> Result<()> {
system_manager::activate::deactivate()
}
fn copy_closure(store_path: &StorePath, target_host: &Option<String>) -> Result<()> {
target_host
.as_ref()
.map_or(Ok(()), |target| do_copy_closure(store_path, target))
}
fn do_copy_closure(store_path: &StorePath, target_host: &str) -> Result<()> {
process::Command::new("nix-copy-closure")
.arg("--to")
.arg(target_host)
.arg("--use-substitutes")
.arg(&store_path.store_path)
.stdout(process::Stdio::inherit())
.stderr(process::Stdio::inherit())
.status()?;
Ok(())
}
fn invoke_remote_script(
store_path: &StorePath,
script_name: &str,
target_host: &str,
use_remote_sudo: bool,
) -> Result<ExitStatus> {
let mut cmd = process::Command::new("ssh");
cmd.arg(target_host).arg("--");
if use_remote_sudo {
cmd.arg("sudo");
}
cmd.arg(format!("{store_path}/bin/{script_name}"))
.stdout(process::Stdio::inherit())
.stderr(process::Stdio::inherit())
.status()
.map_err(anyhow::Error::from)
}
fn check_root() -> Result<()> {
if !nix::unistd::Uid::is_root(nix::unistd::getuid()) {
anyhow::bail!("We need root permissions.")