Skip to content
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
105 changes: 105 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# 馃崷 Nilla Home

> Work with Home Manager configurations in [Nilla](https://github.com/nilla-nix/nilla) projects with ease.

## Integration with Nilla CLI

Nilla Home integrates with the [Nilla CLI](https://github.com/nilla-nix/cli) through its external subcommand mechanism. When you run `nilla home`, the Nilla CLI looks for a binary named `nilla-home` in your PATH and executes it with the remaining arguments.

### How It Works

The Nilla CLI supports external subcommands via the `external_subcommand` mechanism. When you run `nilla <subcommand>`, it searches for a binary named `nilla-<subcommand>` in your PATH. Once `nilla-home` is installed (using one of the methods below), it will be available as `nilla-home` and can be used via the Nilla CLI.

### Commands

Once installed, you can use it via the Nilla CLI:

```bash
# Build a Home Manager configuration
nilla home build <specifier>

# Build and switch to a configuration
nilla home switch <specifier>

# Pass additional nix build options
nilla home build <specifier> -- --builders "ssh://remote x86_64-linux"
nilla home switch <specifier> -- --builders "ssh://remote x86_64-linux"
```

The `<specifier>` follows the format `{username}[@hostname][:system]`, for example:

- `user` - for the current user on the current hostname
- `user@hostname` - for a specific user on a specific hostname
- `user@hostname:x86_64-linux` - with an explicit system architecture

## Install with Nilla

You can add Nilla Home to your Nilla project and access using the following code:

```nix
# In any module of your project.
{ config }:
let
nilla-home-package = config.inputs.nilla-home.packages.nilla-home.x86_64-linux;
in
{
config = {
inputs.nilla-home.src = builtins.fetchTarball {
url = "https://github.com/nilla-nix/home/archive/main.tar.gz";
sha256 = "0000000000000000000000000000000000000000000000000000";
};

# Do something with the package.
};
}
```

## Install without Flakes

You can install Nilla Home in your NixOS, home-manager, or nix-darwin configuration.

```nix
# configuration.nix
{ pkgs, ... }:
let
nilla-home = import (builtins.fetchTarball {
url = "https://github.com/nilla-nix/home/archive/main.tar.gz";
sha256 = "0000000000000000000000000000000000000000000000000000";
});
nilla-home-package = nilla-home.packages.nilla-home.result.${pkgs.system};
in
{
environment.systemPackages = [
nilla-home-package
];
}
```

## Install with Flakes

You can add Nilla Home as a Flake input.

```nix
# flake.nix
{
inputs = {
nilla-home.url = "github:nilla-nix/home";
};

outputs = { nilla-home, ... }:
let
nilla-home-package = nilla-home.packages.x86_64-linux.nilla-home;
in
# Do something with the package.
{};
}
```

## Run with Flakes

You can run Nilla Home directly via Flakes.

```bash
# Place any arguments you want to provide to Nilla Home after the --
nix run github:nilla-nix/home -- --help
```
7 changes: 6 additions & 1 deletion home-cli-def/src/commands/build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use clap::Args;
#[derive(Debug, Args)]
#[command(about = "Build a home")]
#[command(
about = "Build a home",
long_about = "Build a home. Additional nix build options can be passed after --, e.g.: nilla home build -- --builders \"ssh://remote x86_64-linux\""
)]
pub struct BuildArgs {
#[arg(help = "Home specifier, in the format {username}[@hostname][:system]")]
pub specifier: Option<String>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true, help = "Additional arguments to pass to nix build")]
pub extra_nix_build_args: Vec<String>,
}
7 changes: 6 additions & 1 deletion home-cli-def/src/commands/switch.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
use clap::Args;

#[derive(Debug, Args)]
#[command(about = "Build, install, and switch into a home")]
#[command(
about = "Build, install, and switch into a home",
long_about = "Build, install, and switch into a home. Additional nix build options can be passed after --, e.g.: nilla home switch -- --builders \"ssh://remote x86_64-linux\""
)]
pub struct SwitchArgs {
#[arg(help = "Home specifier, in the format {username}[@hostname][:system]")]
pub specifier: Option<String>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true, help = "Additional arguments to pass to nix build")]
pub extra_nix_build_args: Vec<String>,
}
28 changes: 10 additions & 18 deletions src/commands/build.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
use log::{debug, error, info};
use log::error;

use crate::{get_home_specifier_and_system, util::nix};
use crate::commands::common;

pub async fn build_cmd(cli: &home_cli_def::Cli, args: &home_cli_def::commands::build::BuildArgs) {
debug!("Resolving project {}", cli.project);
let Ok(project) = crate::util::project::resolve(&cli.project).await else {
return error!("Could not find project {}", cli.project);
let (path, entry) = match common::get_nilla_nix_path(&cli.project).await {
Ok(p) => p,
Err(e) => return error!("{}", e),
};

let entry = project.clone().get_entry();
let mut path = project.get_path();

debug!("Resolved project {path:?}");

path.push("nilla.nix");

match path.try_exists() {
Ok(false) | Err(_) => return error!("File not found"),
_ => {}
}

let (specifier, system) = match get_home_specifier_and_system(
entry,
&args.specifier.clone().unwrap_or("".to_owned()),
Expand All @@ -30,16 +19,19 @@ pub async fn build_cmd(cli: &home_cli_def::Cli, args: &home_cli_def::commands::b
Err(e) => return error!("{:?}", e),
};

let attribute = format!("homes.\"{specifier}\".result.\"{system}\".activationPackage");
let attribute = common::format_home_attribute(&specifier, &system);
let builders = crate::util::args::extract_builders_from_args(&args.extra_nix_build_args);

common::log_build_operation(&specifier, builders.as_ref());

info!("Building home {specifier}");
let out = nix::build(
&path,
&attribute,
nix::BuildOpts {
link: true,
report: true,
system: Some(system.as_str()),
extra_args: &args.extra_nix_build_args,
},
)
.await;
Expand Down
43 changes: 43 additions & 0 deletions src/commands/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use log::{debug, error, info};
use std::path::PathBuf;

use crate::util::nix::FixedOutputStoreEntry;

/// Get the path to nilla.nix file from the project
pub async fn get_nilla_nix_path(project: &str) -> Result<(PathBuf, FixedOutputStoreEntry), String> {
debug!("Resolving project {}", project);
let project_resolved = crate::util::project::resolve(project)
.await
.map_err(|_| format!("Could not find project {}", project))?;

let entry = project_resolved.clone().get_entry();
let mut path = project_resolved.get_path();
debug!("Resolved project {path:?}");

path.push("nilla.nix");

match path.try_exists() {
Ok(false) | Err(_) => Err("File not found".to_string()),
_ => Ok((path, entry)),
}
}

/// Format the home attribute path for a specifier and system
pub fn format_home_attribute(specifier: &str, system: &str) -> String {
format!("homes.\"{specifier}\".result.\"{system}\".activationPackage")
}

/// Format location string for logging
fn format_location(builders: Option<&String>) -> &str {
builders.map(|b| b.as_str()).unwrap_or("locally")
}

/// Log build operation with location information
pub fn log_build_operation(specifier: &str, builders: Option<&String>) {
let build_location = format_location(builders);
if builders.is_some() {
info!("Building home {specifier} with builders: {build_location}");
} else {
info!("Building home {specifier} locally");
}
}
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod build;
pub mod common;
pub mod switch;
33 changes: 11 additions & 22 deletions src/commands/switch.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,18 @@
use log::{debug, error, info};
use log::{error, info};
use tokio::process::Command;

use crate::{
get_home_specifier_and_system,
util::nix::{self},
};
use crate::{get_home_specifier_and_system, util::nix};
use crate::commands::common;

pub async fn switch_cmd(
cli: &home_cli_def::Cli,
args: &home_cli_def::commands::switch::SwitchArgs,
) {
debug!("Resolving project {}", cli.project);
let Ok(project) = crate::util::project::resolve(&cli.project).await else {
return error!("Could not find project {}", cli.project);
let (path, entry) = match common::get_nilla_nix_path(&cli.project).await {
Ok(p) => p,
Err(e) => return error!("{}", e),
};

let entry = project.clone().get_entry();
let mut path = project.get_path();

debug!("Resolved project {path:?}");

path.push("nilla.nix");

match path.try_exists() {
Ok(false) | Err(_) => return error!("File not found"),
_ => {}
}

let (specifier, system) = match get_home_specifier_and_system(
entry,
&args.specifier.clone().unwrap_or("".to_owned()),
Expand All @@ -37,16 +23,19 @@ pub async fn switch_cmd(
Err(e) => return error!("{:?}", e),
};

let attribute = format!("homes.\"{specifier}\".result.\"{system}\".activationPackage");
let attribute = common::format_home_attribute(&specifier, &system);
let builders = crate::util::args::extract_builders_from_args(&args.extra_nix_build_args);

common::log_build_operation(&specifier, builders.as_ref());

info!("Building home {specifier}");
let out = nix::build(
&path,
&attribute,
nix::BuildOpts {
link: true,
report: true,
system: Some(system.as_str()),
extra_args: &args.extra_nix_build_args,
},
)
.await;
Expand Down
27 changes: 27 additions & 0 deletions src/util/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// nix build flag for specifying remote builders
pub const BUILDERS_FLAG: &str = "--builders";

/// Extract a value from command-line arguments for a given flag.
///
/// Supports both `--flag value` and `--flag=value` formats.
pub fn extract_value_from_args(args: &[String], flag: &str) -> Option<String> {
for (i, arg) in args.iter().enumerate() {
if arg == flag {
// Check if next argument is the value
if let Some(next) = args.get(i + 1) {
if !next.starts_with('-') {
return Some(next.clone());
}
}
} else if arg.starts_with(&format!("{}=", flag)) {
// Handle --flag=value format
return arg.split('=').nth(1).map(|s| s.to_string());
}
}
None
}

/// Extract builders information from arguments.
pub fn extract_builders_from_args(args: &[String]) -> Option<String> {
extract_value_from_args(args, BUILDERS_FLAG)
}
1 change: 1 addition & 0 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod args;
pub mod errors;
pub mod git;
pub mod nix;
Expand Down
7 changes: 7 additions & 0 deletions src/util/nix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ pub struct BuildOpts<'a> {
pub link: bool,
pub report: bool,
pub system: Option<&'a str>,
pub extra_args: &'a [String],
}

pub async fn build<P>(file: P, name: &str, opts: BuildOpts<'_>) -> Result<Vec<String>>
Expand All @@ -286,6 +287,12 @@ where
args.push(system);
};
args.push(&name);

// Add extra arguments
for arg in opts.extra_args {
args.push(arg);
}

debug!("Running nix build:\nnix {}", args.join(" "));
let cmd = Command::new("nix")
.stdout(Stdio::piped())
Expand Down