Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(step): nix-helper #1045

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ notify-rust = "~4.11"
wildmatch = "2.3.0"
rust-i18n = "3.0.1"
sys-locale = "0.3.1"
whoami = "1.5.2"

[package.metadata.generate-rpm]
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
Expand Down
18 changes: 18 additions & 0 deletions locales/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -793,3 +793,21 @@ _version: 2
es: "No se pueden actualizar las aplicaciones de Microsoft Store, se requiere intervención manual"
fr: "Impossible de mettre à jour les applications du Microsoft Store, une intervention manuelle est nécessaire"
zh_TW: "無法更新 Microsoft Store 應用,需手動幹預"

"No flake configurations found for nh":
en: "No flake configurations found for nh"
es: "No se encontraron configuraciones de flake para nh."
fr: "Aucune configuration de flake trouvée pour nh."
zh_TW: "没有为 nh 找到 flake 配置"

"No username found":
en: "No username found"
es: "No se encontró ningún nombre de usuario."
fr: "Aucun nom d'utilisateur trouvé."
zh_TW: "未找到用户名"

"No hostname found":
en: "No hostname found"
es: "No se encontró ningún nombre de host."
fr: "Aucun nom d'hôte trouvé."
zh_TW: "未找到主机名"
1 change: 1 addition & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ pub enum Step {
Mise,
Myrepos,
Nix,
NixHelper,
Node,
Opam,
Pacdef,
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ fn run() -> Result<()> {
runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?;
runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?;
runner.execute(Step::Nix, "nix upgrade-nix", || unix::run_nix_self_upgrade(&ctx))?;
runner.execute(Step::NixHelper, "nh", || unix::run_nix_helper(&ctx))?;
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(&ctx))?;
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?;
Expand Down
88 changes: 87 additions & 1 deletion src/steps/os/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use regex::Regex;
use rust_i18n::t;
use semver::Version;
use tracing::debug;
use whoami::fallible::hostname;
use whoami::fallible::username;

#[cfg(target_os = "linux")]
use super::linux::Distribution;
Expand Down Expand Up @@ -620,8 +622,92 @@ fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
)
}

/// Update NixOS and home-manager through a flake using `nh`
///
/// See: https://github.com/viperML/nh
pub fn run_nix_helper(ctx: &ExecutionContext) -> Result<()> {
let nix = require("nix")?;
let nix_helper = require("nh")?;
let flake_path: PathBuf = std::env::var_os("FLAKE")
.ok_or_else(|| SkipStep("$FLAKE not set".into()))?
.require()?
.into();

flake_path.join("flake.nix").require()?;

let run_type = ctx.run_type();

// we never dry run this because it doesn't perform any changes and because it ensures that the
// behaviour between dry and wet runs is the same i.e. the checks performed are the same
// between dry and wet runs
let run_nix_eval = |attr| -> Result<String> {
Ok(String::from_utf8(
std::process::Command::new(&nix)
.args(nix_args())
.arg("eval")
.arg(format!("{}#{attr}", flake_path.display()))
.arg("--apply")
.arg("builtins.attrNames")
.output_checked()?
.stdout,
)?)
};

let nixos_cfg = run_nix_eval("nixosConfigurations");
let home_cfg = run_nix_eval("homeConfigurations");

if nixos_cfg.as_ref().or(home_cfg.as_ref()).is_err() && !run_type.dry() {
return Err(SkipStep(t!("No flake configurations found for nh").to_string()).into());
}

print_separator("nh flake");
run_type
.execute(&nix)
.args(nix_args())
.arg("flake")
.arg("update")
.arg("--flake")
.arg(&flake_path)
.status_checked()?;

let username = username().wrap_err_with(|| t!("No username found").to_string())?;
let hostname = hostname().wrap_err_with(|| t!("No hostname found").to_string())?;

// Let's not do any JSON parsing and just Keep It Simple
if nixos_cfg.is_ok_and(|x| x.contains(&format!(r#""{hostname}""#))) {
print_separator("nh os");

let mut cmd = run_type.execute(&nix_helper);
cmd.arg("os");
cmd.arg("switch");

if !ctx.config().yes(Step::NixHelper) {
cmd.arg("--ask");
}
cmd.status_checked()?;
}

if let Ok(home_cfg) = home_cfg {
let username_at_hostname = format!(r#""{username}@{hostname}""#);
if home_cfg.contains(&format!(r#""{username}""#)) || home_cfg.contains(&username_at_hostname) {
print_separator("nh home");

let mut cmd = run_type.execute(&nix_helper);
cmd.arg("home");
cmd.arg("switch");

if !ctx.config().yes(Step::NixHelper) {
cmd.arg("--ask");
}
cmd.status_checked()?;
}
}

Ok(())
}

fn nix_args() -> [&'static str; 2] {
["--extra-experimental-features", "nix-command"]
["--extra-experimental-features", "nix-command flakes"]
}

pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
Expand Down
Loading