Skip to content

Commit

Permalink
Add apply nix-darwin command
Browse files Browse the repository at this point in the history
  • Loading branch information
lucperkins committed Jul 24, 2024
1 parent 0d43955 commit 7e8a131
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 86 deletions.
10 changes: 5 additions & 5 deletions src/cli/cmd/apply/home_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ impl HomeManager {

// This function enables you to provide simplified paths:
//
// fh apply home-manager omnicorp/home/0
// fh apply home-manager omnicorp/home/0.1
//
// Here, `omnicorp/systems/0`` resolves to `omnicorp/systems/0#homeConfigurations.$(whoami)`.
// Here, `omnicorp/systems/0.1` resolves to `omnicorp/systems/0.1#homeConfigurations.$(whoami)`.
// If you need to apply a configuration at a path that doesn't conform to this pattern, you
// can still provide an explicit path.
fn parse_output_ref(path: &str) -> Result<String, FhError> {
Expand All @@ -31,7 +31,7 @@ fn parse_output_ref(path: &str) -> Result<String, FhError> {
Ok(match path.split('#').collect::<Vec<_>>()[..] {
[_release, _output_path] => parse_release_ref(path)?,
[release] => format!("{release}#homeConfigurations.{username}"),
_ => return Err(FhError::MalformedNixOSConfigPath(path.to_string())),
_ => return Err(FhError::MalformedOutputRef(path.to_string())),
})
}

Expand All @@ -53,8 +53,8 @@ mod tests {
format!("foo/bar/0.1.*#homeConfigurations.{username}"),
),
(
"omnicorp/web/0.1.2#homeConfigurations.auth-server",
"omnicorp/web/0.1.2#homeConfigurations.auth-server".to_string(),
"omnicorp/web/0.1.2#homeConfigurations.my-config",
"omnicorp/web/0.1.2#homeConfigurations.my-config".to_string(),
),
(
"omnicorp/web/0.1.2#packages.x86_64-linux.default",
Expand Down
147 changes: 76 additions & 71 deletions src/cli/cmd/apply/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod home_manager;
mod nix_darwin;
mod nixos;

use std::{os::unix::prelude::PermissionsExt, path::PathBuf, process::ExitCode};
Expand All @@ -16,7 +17,8 @@ use crate::{

use self::{
home_manager::{HomeManager, HOME_MANAGER_SCRIPT},
nixos::{NixOS, NIXOS_PROFILE, NIXOS_SCRIPT},
nix_darwin::{NixDarwin, NIX_DARWIN_ACTION, NIX_DARWIN_SCRIPT},
nixos::{NixOs, NIXOS_PROFILE, NIXOS_SCRIPT},
};

use super::{CommandExecute, FlakeHubClient};
Expand All @@ -33,20 +35,24 @@ pub(crate) struct ApplySubcommand {

#[derive(Subcommand)]
enum System {
/// Resolve the store path for a Home Manager configuration and run its activation script
HomeManager(HomeManager),

/// Resolve the store path for a nix-darwin configuration and run its activation script
NixDarwin(NixDarwin),

/// Apply the resolved store path on a NixOS system
#[clap(name = "nixos")]
NixOS(NixOS),

/// Resolve the store path for a Home Manager configuration and run its activate script
HomeManager(HomeManager),
NixOs(NixOs),
}

#[async_trait::async_trait]
impl CommandExecute for ApplySubcommand {
async fn execute(self) -> color_eyre::Result<ExitCode> {
let output_ref = match &self.system {
System::NixOS(nixos) => nixos.output_ref()?,
System::NixOs(nixos) => nixos.output_ref()?,
System::HomeManager(home_manager) => home_manager.output_ref()?,
System::NixDarwin(nix_darwin) => nix_darwin.output_ref()?,
};

tracing::info!("Resolving store path for output: {}", output_ref);
Expand All @@ -62,82 +68,81 @@ impl CommandExecute for ApplySubcommand {
);

match self.system {
System::NixOS(NixOS { action, .. }) => {
System::HomeManager(_) => {
// /nix/store/{path}/activate
let script_path = path!(&resolved_path.store_path, HOME_MANAGER_SCRIPT);
run_script(script_path, None, HOME_MANAGER_SCRIPT).await?;
}
System::NixDarwin(_) => {
// /nix/store/{path}/sw/bin/darwin-rebuild
let script_path = path!(&resolved_path.store_path, "sw", "bin", NIX_DARWIN_SCRIPT);
run_script(
script_path,
Some(NIX_DARWIN_ACTION.to_string()),
NIX_DARWIN_SCRIPT,
)
.await?;
}
System::NixOs(NixOs { action, .. }) => {
let profile_path =
apply_path_to_profile(NIXOS_PROFILE, &resolved_path.store_path).await?;

let script_path = path!(&profile_path, "bin", NIXOS_SCRIPT);

tracing::debug!(
"Checking for {} script at {}",
NIXOS_SCRIPT,
&script_path.display().to_string(),
);

if script_path.exists() && script_path.is_file() {
tracing::debug!(
"Found {} script at {}",
NIXOS_SCRIPT,
&script_path.display().to_string(),
);

if let Ok(script_path_metadata) = tokio::fs::metadata(&script_path).await {
let permissions = script_path_metadata.permissions();
if permissions.mode() & 0o111 != 0 {
tracing::info!(
"{} {}",
&script_path.display().to_string(),
action.to_string(),
);

// switch-to-configuration <action>
let output = tokio::process::Command::new(&script_path)
.args([&action.to_string()])
.output()
.await
.wrap_err("failed to run switch-to-configuration script")?;

println!("{}", String::from_utf8_lossy(&output.stdout));
}
}
}
run_script(script_path, Some(action.to_string()), NIXOS_SCRIPT).await?;
}
System::HomeManager(_) => {
let script_path = path!(&resolved_path.store_path, HOME_MANAGER_SCRIPT);
}

tracing::debug!(
"Checking for {} script at {}",
HOME_MANAGER_SCRIPT,
&script_path.display().to_string(),
);

if script_path.exists() && script_path.is_file() {
tracing::debug!(
"Found {} script at {}",
HOME_MANAGER_SCRIPT,
&script_path.display().to_string(),
);

if let Ok(script_path_metadata) = tokio::fs::metadata(&script_path).await {
let permissions = script_path_metadata.permissions();
if permissions.mode() & 0o111 != 0 {
tracing::info!("{}", &script_path.display().to_string());

// activate
let output = tokio::process::Command::new(&script_path)
.output()
.await
.wrap_err("failed to run activate script")?;

println!("{}", String::from_utf8_lossy(&output.stdout));
}
}
Ok(ExitCode::SUCCESS)
}
}

async fn run_script(
script_path: PathBuf,
action: Option<String>,
script_name: &str,
) -> Result<(), FhError> {
tracing::debug!(
"Checking for {} script at {}",
script_name,
&script_path.display().to_string(),
);

if script_path.exists() && script_path.is_file() {
tracing::debug!(
"Found {} script at {}",
script_name,
&script_path.display().to_string(),
);

if let Ok(script_path_metadata) = tokio::fs::metadata(&script_path).await {
let permissions = script_path_metadata.permissions();
if permissions.mode() & 0o111 != 0 {
if let Some(action) = &action {
tracing::info!("{} {}", &script_path.display().to_string(), action);
} else {
tracing::info!("{}", &script_path.display().to_string());
}

let output = if let Some(action) = &action {
tokio::process::Command::new(&script_path)
.arg(action)
.output()
.await
.wrap_err(format!("failed to run {script_name} script"))?
} else {
tokio::process::Command::new(&script_path)
.output()
.await
.wrap_err(format!("failed to run {script_name} script"))?
};

println!("{}", String::from_utf8_lossy(&output.stdout));
}
}

Ok(ExitCode::SUCCESS)
}

Ok(())
}

async fn apply_path_to_profile(profile: &str, store_path: &str) -> Result<String, FhError> {
Expand Down
71 changes: 71 additions & 0 deletions src/cli/cmd/apply/nix_darwin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use clap::Parser;

use crate::cli::{cmd::parse_release_ref, error::FhError};

pub(super) const NIX_DARWIN_ACTION: &str = "activate";
pub(super) const NIX_DARWIN_SCRIPT: &str = "darwin-rebuild";

#[derive(Parser)]
pub(super) struct NixDarwin {
/// The FlakeHub output reference for the nix-darwin configuration.
/// References must take one of two forms: {org}/{flake}/{version_req}#{attr_path} or {org}/{flake}/{version_req}.
/// If the latter, the attribute path defaults to darwinConfigurations.{devicename}.system, where devicename
/// is the output of scutil --get LocalHostName.
pub(super) output_ref: String,
}

impl NixDarwin {
pub(super) fn output_ref(&self) -> Result<String, FhError> {
parse_output_ref(&self.output_ref)
}
}

// This function enables you to provide simplified paths:
//
// fh apply nix-darwin omnicorp/home/0.1
//
// Here, `omnicorp/systems/0.1` resolves to `omnicorp/systems/0#darwinConfigurations.$(devicename).system`.
// If you need to apply a configuration at a path that doesn't conform to this pattern, you
// can still provide an explicit path.
fn parse_output_ref(path: &str) -> Result<String, FhError> {
let devicename = whoami::devicename();

Ok(match path.split('#').collect::<Vec<_>>()[..] {
[_release, _output_path] => parse_release_ref(path)?,
[release] => format!("{release}#darwinConfigurations.{devicename}.system"),
_ => return Err(FhError::MalformedOutputRef(path.to_string())),
})
}

#[cfg(test)]
mod tests {
use crate::cli::cmd::apply::nix_darwin::parse_output_ref;

#[test]
fn test_parse_nixos_output_ref() {
let devicename = whoami::devicename();

let cases: Vec<(&str, String)> = vec![
(
"foo/bar/*",
format!("foo/bar/*#darwinConfigurations.{devicename}.system"),
),
(
"foo/bar/0.1.*",
format!("foo/bar/0.1.*#darwinConfigurations.{devicename}.system"),
),
(
"omnicorp/web/0.1.2#darwinConfigurations.my-config",
"omnicorp/web/0.1.2#darwinConfigurations.my-config".to_string(),
),
(
"omnicorp/web/0.1.2#packages.x86_64-linux.default",
"omnicorp/web/0.1.2#packages.x86_64-linux.default".to_string(),
),
];

for case in cases {
assert_eq!(parse_output_ref(case.0).unwrap(), case.1);
}
}
}
16 changes: 8 additions & 8 deletions src/cli/cmd/apply/nixos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub(super) const NIXOS_PROFILE: &str = "system";
pub(super) const NIXOS_SCRIPT: &str = "switch-to-configuration";

#[derive(Parser)]
pub(super) struct NixOS {
pub(super) struct NixOs {
/// The FlakeHub output reference to apply to the system profile.
/// References must take one of two forms: {org}/{flake}/{version_req}#{attr_path} or {org}/{flake}/{version_req}.
/// If the latter, the attribute path defaults to nixosConfigurations.{hostname}.
Expand All @@ -17,10 +17,10 @@ pub(super) struct NixOS {
/// The command to run from the profile's switch-to-configuration script.
/// Takes the form: switch-to-configuration <action>.
#[clap(name = "ACTION", default_value = "switch")]
pub(super) action: Verb,
pub(super) action: NixOsAction,
}

impl NixOS {
impl NixOs {
pub(super) fn output_ref(&self) -> Result<String, FhError> {
parse_output_ref(&self.output_ref)
}
Expand All @@ -29,14 +29,14 @@ impl NixOS {
// For available commands, see
// https://github.com/NixOS/nixpkgs/blob/12100837a815473e96c9c86fdacf6e88d0e6b113/nixos/modules/system/activation/switch-to-configuration.pl#L85-L88
#[derive(Clone, Debug, ValueEnum)]
pub enum Verb {
pub enum NixOsAction {
Switch,
Boot,
Test,
DryActivate,
}

impl Display for Verb {
impl Display for NixOsAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
Expand All @@ -53,9 +53,9 @@ impl Display for Verb {

// This function enables you to provide simplified paths:
//
// fh apply nixos omnicorp/systems/0
// fh apply nixos omnicorp/systems/0.1
//
// Here, `omnicorp/systems/0`` resolves to `omnicorp/systems/0#nixosConfigurations.$(hostname)`.
// Here, `omnicorp/systems/0.1` resolves to `omnicorp/systems/0.1#nixosConfigurations.$(hostname)`.
// If you need to apply a configuration at a path that doesn't conform to this pattern, you
// can still provide an explicit path.
fn parse_output_ref(path: &str) -> Result<String, FhError> {
Expand All @@ -64,7 +64,7 @@ fn parse_output_ref(path: &str) -> Result<String, FhError> {
Ok(match path.split('#').collect::<Vec<_>>()[..] {
[_release, _output_path] => parse_release_ref(path)?,
[release] => format!("{release}#nixosConfigurations.{hostname}"),
_ => return Err(FhError::MalformedNixOSConfigPath(path.to_string())),
_ => return Err(FhError::MalformedOutputRef(path.to_string())),
})
}

Expand Down
4 changes: 2 additions & 2 deletions src/cli/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ pub(crate) enum FhError {
#[error("label parsing error: {0}")]
LabelParse(String),

#[error("malformed NixOS configuration path: {0}")]
MalformedNixOSConfigPath(String),
#[error("malformed output reference: {0}")]
MalformedOutputRef(String),

#[error("malformed flake reference")]
MalformedFlakeOutputRef,
Expand Down

0 comments on commit 7e8a131

Please sign in to comment.