From 6b8327faad63453dd30f62eb6c6501b4c8500074 Mon Sep 17 00:00:00 2001 From: Tom van Dijk <18gatenmaker6@gmail.com> Date: Tue, 15 Jul 2025 11:05:46 +0200 Subject: [PATCH] feat(step): nix-helper (#1045) --- locales/app.yml | 16 +++++++++ src/config.rs | 1 + src/main.rs | 1 + src/steps/os/unix.rs | 82 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 99 insertions(+), 1 deletion(-) diff --git a/locales/app.yml b/locales/app.yml index be7153cf..539b85f6 100644 --- a/locales/app.yml +++ b/locales/app.yml @@ -1304,3 +1304,19 @@ _version: 2 zh_CN: "<省略了 `deb-get clean` 的输出>" zh_TW: "<省略了 `deb-get clean` 的輸出>" de: "" +"You have a flake inside of $FLAKE. This is deprecated for nh.": + en: "You have a flake inside of $FLAKE. This is deprecated for nh." + lt: "Jūs turite flake viduje $FLAKE. Tai yra pasenę nh." + es: "Tienes un flake dentro de $FLAKE. Esto está en desuso para nh." + fr: "Vous avez un flake à l'intérieur de $FLAKE. Ça c'est obsolète pour nh." + zh_TW: "你在 $FLAKE 裡有一個 flake。這在 nh 中已被棄用。" + zh_CN: "你在 $FLAKE 里有一个 flake。这在 nh 中已被弃用。" + de: "Sie haben ein flake in $FLAKE. Dies ist für nh veraltet." +"nh cannot find any configured flakes": + en: "nh cannot find any configured flakes" + lt: "nh nepavyksta rasti jokių sukonfigūruotų flake" + es: "nh no puede encontrar ningún flake configurado" + fr: "nh ne peut trouver aucun flake configuré" + zh_TW: "nh 找不到任何已設定的 flake" + zh_CN: "nh 无法找到任何已配置的 flake" + de: "nh kann keine konfigurierten flakes finden" diff --git a/src/config.rs b/src/config.rs index 6bbe3e46..1a6f52f6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -134,6 +134,7 @@ pub enum Step { Mise, Myrepos, Nix, + NixHelper, Node, Opam, Pacdef, diff --git a/src/main.rs b/src/main.rs index 5f78a5ac..5737931f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -314,6 +314,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))?; diff --git a/src/steps/os/unix.rs b/src/steps/os/unix.rs index 3245cf26..60dd0d96 100644 --- a/src/steps/os/unix.rs +++ b/src/steps/os/unix.rs @@ -18,7 +18,7 @@ use std::path::PathBuf; use std::process::Command; use std::sync::LazyLock; use std::{env::var, path::Path}; -use tracing::debug; +use tracing::{debug, warn}; #[cfg(target_os = "linux")] use super::linux::Distribution; @@ -622,6 +622,86 @@ fn nix_profile_dir(nix: &Path) -> Result> { ) } +/// Returns a directory from an environment variable, if and only if it is a directory which +/// contains a flake.nix +fn flake_dir(var: &'static str) -> Option { + std::env::var_os(var) + .map(PathBuf::from) + .take_if(|x| std::fs::exists(x.join("flake.nix")).is_ok_and(|x| x)) +} + +/// 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 run_type = ctx.run_type(); + + require("nix")?; + let nix_helper = require("nh")?; + + let fallback_flake_path = flake_dir("NH_FLAKE"); + let darwin_flake_path = flake_dir("NH_DARWIN_FLAKE"); + let home_flake_path = flake_dir("NH_HOME_FLAKE"); + let nixos_flake_path = flake_dir("NH_OS_FLAKE"); + + let all_flake_paths: Vec<_> = [ + fallback_flake_path.as_ref(), + darwin_flake_path.as_ref(), + home_flake_path.as_ref(), + nixos_flake_path.as_ref(), + ] + .into_iter() + .flatten() + .collect(); + + // if none of the paths exist AND contain a `flake.nix`, skip + if all_flake_paths.is_empty() { + if flake_dir("FLAKE").is_some() { + warn!( + "{}", + t!("You have a flake inside of $FLAKE. This is deprecated for nh.") + ); + } + return Err(SkipStep(t!("nh cannot find any configured flakes").into()).into()); + } + + let nh_switch = |ty: &'static str| -> Result<()> { + print_separator(format!("nh {ty}")); + + let mut cmd = run_type.execute(&nix_helper); + cmd.arg(ty); + cmd.arg("switch"); + cmd.arg("-u"); + + if !ctx.config().yes(Step::NixHelper) { + cmd.arg("--ask"); + } + cmd.status_checked()?; + Ok(()) + }; + + // We assume that if the user has set these variables, we can throw an error if nh cannot find + // a flake there. So we do not anymore perform an eval check to find out wether we should skip + // or not. + #[cfg(target_os = "macos")] + if darwin_flake_path.is_some() || fallback_flake_path.is_some() { + nh_switch("darwin")?; + } + + if home_flake_path.is_some() || fallback_flake_path.is_some() { + nh_switch("home")?; + } + + #[cfg(target_os = "linux")] + if matches!(Distribution::detect(), Ok(Distribution::NixOS)) + && (nixos_flake_path.is_some() || fallback_flake_path.is_some()) + { + nh_switch("os")?; + } + + Ok(()) +} + fn nix_args() -> [&'static str; 2] { ["--extra-experimental-features", "nix-command"] }