Home Manager, shell scripts, and local flakes

I am curious about a nice way to add reproducible shell (etc) scripts which can be brought into my home manager session

It’s easy enough to stick these in in ~/bin or /usr/local/bin on a traditional unix-like system, and I see I could add the scripts into home.nix (or a sub-module) with writeShellApplication, but I like the idea of the flexibility of packaging each one into a flake

One option would be to put the flake scripts in github or some other git repository and import them into my home manager flake, but I prefer darcs for version control. I could perhaps stick them in darcs hub and import them with fetchUrl, but I would like to have the option to keep them local

I understand absolute paths are out, and I don’t mind putting these in a sub-directory, but being rather new to nixos I was wondering whether there was a way to pull in a local flake into home-manager or perhaps a more elegant solution that I am unaware of?

Thanks for any assistance

I wouldn’t put them in separate flakes. That’s completely pointless and will just be a headache to maintain. You get absolutely zero benefit from this, and the increase in build time, update time/effort, metadata file lines, LoC, etc. will all be felt.

Splitting out separate projects makes sense, if you have disjoint sets of contributors and/or users, but the types of scripts that people would put in their $PATH are normally married to their system configuration, and don’t really make sense to split out.


This does not mean you cannot write derivations for your scripts, with or without flakes. Place in your configuration.nix something like:

# configuration.nix
{ pkgs, lib, ... }: let
  scripts = lib.packagesFromDirectoryRecursive {
    inherit (pkgs) callPackage;
    directory = ./packages;
  };
in {
  _module.args = {
    inherit scripts;
  };
}

… then you can create packages in ./packages just like the packages defined in nixpkgs, and they’ll be accessible as scripts.<filename>. So, to e.g. add a little hello-world script to systemPackages from a module you import from configuration.nix:

# packages/hello-world.nix
{
  writers,
  hello,
  ...
}:
writers.writeBashBin "hello-world" {
  makeWrapperArgs = [
    "--prefix" "PATH" ":" "${lib.makeBinPath [ hello ]}"
  ];
} ''
  hello
''
Or alternatively if you want to separate out the script file
# packages/hello-world/package.nix
{
  writers,
  hello,
  ...
}:
writers.writeBashBin "hello-world" {
  makeWrapperArgs = [
    "--prefix" "PATH" ":" "${lib.makeBinPath [ hello ]}"
  ];
} ./hello-world.bash
#!/usr/bin/env bash
# packages/hello-world/hello-world.bash

# The shebang won't actually be used, but
# I like including one anyway.
hello
# config/some-module.nix
{ scripts, ... }: {
  environment.systemPackages = [
    scripts.hello-world
  ];
}

With flakes, you can absolutely put this same attrset in the packages output of your system flake, if you so desire. This will limit you to zero nesting, though.

I currently do this, but realistically, you won’t have any consumers, so adding it to your flake metadata is pointless (as is, frankly, using flakes for NixOS configuration). I intend to move away from exposing my local packages with the packages output, and I would not recommend anyone does so with a flake whose purpose is NixOS deployments.

3 Likes

Thanks so much for your help

I do have a few more questions. I would like these scripts to be available for my default shell environment, but I do not see them on my path

I hope I was able to follow your suggestion, but for clarity let me share what I have done:

in my stand-alone ~/.config/home-manager, with flake.nix and home.nix:

# ~/.config/home-manager/flake.nix
{
  description = "Home Manager configuration";
  
  inputs = {
    # Specify the source of Home Manager and Nixpkgs.
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };
  
  outputs = { nixpkgs, home-manager, ... }:
    let
      system  = "x86_64-linux";
      pkgs    = nixpkgs.legacyPackages.${system};
      lib     = nixpkgs.lib;
      homeCfg = home-manager.lib.homeManagerConfiguration;
      scripts = lib.packagesFromDirectoryRecursive {
        inherit (pkgs) callPackage;
        directory = ./package;
      };
    in {
       module.args = {
         inherit scripts;
         modules = [ ./config/script.nix ];
      };
      homeConfigurations.petrockette = homeCfg {
        inherit pkgs;
        modules = [ ./home.nix ];
      };
    };
}
# ~/.config/home-manager/package/hello-world.nix
{ writers, hello, ... }:
writers.writeBashBin "hello-world" {
  makeWrapperArgs = [ "--prefix" "PATH" ":" "${lib.makeBinPath [ hello ]}" ];
} ''
hello
''
# ~/.config/home-manager/package/cow/fortunate-cow.nix
{ writers, fortune, cowsay, ... }:
writers.writeBashBin "fortunate-cow" {
  makeWrapperArgs = [
    "--prefix" "PATH" ":" "${lib.makeBinPath [ fortune cowsay ]}"
  ]; 
} ./fortunate-cow
#!/usr/bin/env bash
# ~/.config/home-manager/package/cow/fortunate-cow

cowsay `fortune`
# ~/.config/home-manager/config/script.nix
{ scripts, ... }: {
  environment.systemPackages = [
    scripts.hello-world
    scripts.cow.fortunate-cow
  ];
}

This all builds/switches fine, but I am at a loss as to how to make them availible in my PATH

Thanks again!

Ah. I don’t recommend using home-manager standalone on NixOS.


But to solve your immediate problem, this is my diff:

diff --git a/home-manager/config/script.nix b/home-manager/config/script.nix
index b5e480b..e7c97a1 100644
--- a/home-manager/config/script.nix
+++ b/home-manager/config/script.nix
@@ -1,6 +1,6 @@
 # ~/.config/home-manager/config/script.nix
 { scripts, ... }: {
-  environment.systemPackages = [
+  home.packages = [
     scripts.hello-world
     scripts.cow.fortunate-cow
   ];
diff --git a/home-manager/flake.nix b/home-manager/flake.nix
index 42adc74..1d0c1bf 100644
--- a/home-manager/flake.nix
+++ b/home-manager/flake.nix
@@ -22,13 +22,13 @@
         directory = ./package;
       };
     in {
-       module.args = {
-         inherit scripts;
-         modules = [ ./config/script.nix ];
-      };
       homeConfigurations.petrockette = homeCfg {
         inherit pkgs;
-        modules = [ ./home.nix ];
+        extraSpecialArgs = { inherit scripts; };
+        modules = [
+          ./home.nix
+          ./config/script.nix
+        ];
       };
     };
 }
diff --git a/home-manager/package/cow/fortunate-cow.nix b/home-manager/package/cow/fortunate-cow.nix
index 0b262ce..9cc7b81 100644
--- a/home-manager/package/cow/fortunate-cow.nix
+++ b/home-manager/package/cow/fortunate-cow.nix
@@ -1,5 +1,5 @@
 # ~/.config/home-manager/package/cow/fortunate-cow.nix
-{ writers, fortune, cowsay, ... }:
+{ lib, writers, fortune, cowsay, ... }:
 writers.writeBashBin "fortunate-cow" {
   makeWrapperArgs = [
     "--prefix" "PATH" ":" "${lib.makeBinPath [ fortune cowsay ]}"
diff --git a/home-manager/package/hello-world.nix b/home-manager/package/hello-world.nix
index 85ff9d9..69734ce 100644
--- a/home-manager/package/hello-world.nix
+++ b/home-manager/package/hello-world.nix
@@ -1,5 +1,5 @@
 # ~/.config/home-manager/package/hello-world.nix
-{ writers, hello, ... }:
+{ lib, writers, hello, ... }:
 writers.writeBashBin "hello-world" {
   makeWrapperArgs = [ "--prefix" "PATH" ":" "${lib.makeBinPath [ hello ]}" ];
 } ''
If you want to clean up `flake.nix`, you can also apply this patch afterwards to get exactly what I intended

It won’t apply cleanly, because I don’t know what’s in your home.nix currently, but looking at the diff should give you an idea of what I meant.

diff --git a/home-manager/flake.nix b/home-manager/flake.nix
index 1d0c1bf..f375e07 100644
--- a/home-manager/flake.nix
+++ b/home-manager/flake.nix
@@ -12,23 +12,10 @@
   };
 
   outputs = { nixpkgs, home-manager, ... }:
-    let
-      system  = "x86_64-linux";
-      pkgs    = nixpkgs.legacyPackages.${system};
-      lib     = nixpkgs.lib;
-      homeCfg = home-manager.lib.homeManagerConfiguration;
-      scripts = lib.packagesFromDirectoryRecursive {
-        inherit (pkgs) callPackage;
-        directory = ./package;
-      };
-    in {
-      homeConfigurations.petrockette = homeCfg {
-        inherit pkgs;
-        extraSpecialArgs = { inherit scripts; };
-        modules = [
-          ./home.nix
-          ./config/script.nix
-        ];
+    {
+      homeConfigurations.petrockette = home-manager.lib.homeManagerConfiguration {
+        pkgs = nixpkgs.legacyPackages.x86_64-linux;
+        modules = [ ./home.nix ];
       };
     };
 }
diff --git a/home-manager/home.nix b/home-manager/home.nix
index 1b251b5..9cad643 100644
--- a/home-manager/home.nix
+++ b/home-manager/home.nix
@@ -1,4 +1,14 @@
+{ pkgs, lib, ... }:
+let
+  scripts = lib.packagesFromDirectoryRecursive {
+    inherit (pkgs) callPackage;
+    directory = ./package;
+  };
+in
 {
+  _module.args = { inherit scripts; };
+  imports = [ ./config/script.nix ];
+
   home = {
     username = "petrockette";
     homeDirectory = "/home/petrockette";

Finally, I can really recommend figuring out how to run nixfmt on your code. Your hand-style isn’t bad, but it saves effort hand-placing spaces, and makes my life easier when I edit your code later :wink:

1 Like

Thats a you thing. There are valid reasons to keep standalone, even on NixOS.

Main reason being not to have to elevate for simple user config changes, secondary reason is the reduced evaluation times.

Of course there are also drawbacks, like out of sync inputs. This effect might be wanted though.

3 Likes

Yep, but I find it’s usually not intended by newbies, harder to explain and more maintenance effort. If you know what you’re doing, go right ahead.

Thanks again!

I was able to incorporate your changes and get both scripts working on path. However, I did have to rename package/cow/package.nix to package/cow/fortunate-cow.nix

before the rename, neither

{ scripts, ... }:
{
  home.packages = [
    scripts.hello-world
    scripts.cow.fortunate-cow
  ];
}

nor

{ scripts, ... }:
{
  home.packages = [
    scripts.hello-world
    scripts.cow.package
  ];
}

would build successfully, with either error: attribute 'fortunate-cow' missing
or error: attribute 'package' missing which is a bit of a mystery to me, being more comfortable with languages like Haskell and Agda.

I had initially incorporated my home manager into my /etc/nixos/flake.nix, but I couldn’t find a way to split out the configuration so that each user could edit their home configuration without needing root elevation. Perhaps I am missing a method: I could try making per-user sub-directories under /etc/nixos and changing the ownership? But then they would still need root to rebuild / switch

I’m not opposed to auto-formatting, especially on collaborative projects to eliminate semantically idempotent differences and have a uniform style, and it does save some effort, physical and mental, never needing to even think about formatting.

But for my own work, I’ve never found one that didn’t at times make some horrible formatting decision

Here is a fortunate cow of gratitude for your amazing assistance:

petrockette@felicette:~$ fortunate-cow
 ______________________________________
/ We are not loved by our friends for  \
| what we are; rather, we are lived in |
\ spite of what we are. -- Victor Hugo /
 --------------------------------------
    \   ^__^
     \  (oo)\_______
        (__)\       )\/\
            ||----w |
            ||     ||
1 Like

Yes, those were the names in your original code. If you wanted to use package.nix, the resulting package would be simply in scripts.cow (without .package or .fortune-cow).

This isn’t a nix language detail, but a callPackagesRecursive detail. Basically, if you name your file package.nix the resulting package has the same name as its parent directory.


Fair enough regarding the other points; if you have multiple users it makes sense to use home-manager standalone, and yep, if you prefer not to use autoformatting that’s fine too, just figured I’d point you at the standard tooling.

I discovered nixfmt when I added nix-mode to emacs. Looking into how much of my emacs config should be governed by nix may be my next step. use-package and straight seem to offer much of what nix management could provide, and emacs configuration being a bunch of elisp I’m not sure how the nix-lang could accommodate, unless you take an approach similar to what we did for the shell scrips–essentially wrappers around elisp configuration snippets

I agree, I wouldn’t want to write my emacs config in nix (or I would, if emacs supported this natively, but it doesn’t, and I’d prefer other languages than lisp or nix).

You’ll like how I do emacs: dotfiles/pkgs/packages/emacs/package.nix at 3f40a836d626e66bb710e0b2cf193363ac16a9e1 · TLATER/dotfiles · GitHub

Essentially, all configuration is done as normal, and packages are selected with leaf - however, nix handles installing the packages for reproducibility and cross-system management, plus I can put extra stuff like language servers in scope cleanly without hacks like the install-system emacs package.

But we’re getting off-topic, feel free to start a new one :wink: