Skip to content

Instantly share code, notes, and snippets.

@InternetUnexplorer
Last active March 17, 2024 11:53
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save InternetUnexplorer/58e979642102d66f57188764bbf11701 to your computer and use it in GitHub Desktop.
Save InternetUnexplorer/58e979642102d66f57188764bbf11701 to your computer and use it in GitHub Desktop.
Improved home-manager nix-locate configuration, with an auto-updated index and a better command-not-found handler
# Adapted from https://github.com/bennofs/nix-index/blob/master/command-not-found.sh
command_not_found_handle () {
if [ -n "${MC_SID-}" ] || ! [ -t 1 ]; then
>&2 echo "$1: command not found"
return 127
fi
echo -n "searching nix-index..."
ATTRS=$(@nix-locate@ --minimal --no-group --type x --type s --top-level --whole-name --at-root "/bin/$1")
case `echo -n "$ATTRS" | grep -c "^"` in
0)
>&2 echo -ne "$(@tput@ el1)\r"
>&2 echo "$1: command not found"
;;
*)
>&2 echo -ne "$(@tput@ el1)\r"
>&2 echo "The program ‘$(@tput@ setaf 4)$1$(@tput@ sgr0)’ is currently not installed."
>&2 echo "It is provided by the following derivation(s):"
while read ATTR; do
ATTR=$(echo "$ATTR" | sed 's|\.out$||') # Strip trailing '.out'
>&2 echo " $(@tput@ setaf 12)nixpkgs#$(@tput@ setaf 4)$ATTR$(@tput@ sgr0)"
done <<< "$ATTRS"
esac
return 127
}
command_not_found_handler () {
command_not_found_handle $@
return $?
}
{ config, pkgs, lib, ... }:
let
# Automatically download the latest index from Mic92's nix-index-database.
nix-locate = pkgs.writeShellScriptBin "nix-locate" ''
set -euo pipefail
mkdir -p ~/.cache/nix-index && cd ~/.cache/nix-index
# Check for updates at most once a day
if [ ! -f last-check ] || [ $(find last-check -mtime +1) ]; then
filename="index-x86_64-$(uname | tr A-Z a-z)"
# Prevent partial downloads
[ -f files ] || rm -f $filename
rm -f files
wget -q -N --show-progress \
https://github.com/Mic92/nix-index-database/releases/latest/download/$filename
ln -sf $filename files
touch last-check
fi
exec ${pkgs.nix-index}/bin/nix-locate "$@"
'';
# Modified version of command-not-found.sh that uses our wrapped version of
# nix-locate, makes the output a bit less noisy, and adds color!
command-not-found = pkgs.runCommandLocal "command-not-found.sh" { } ''
mkdir -p $out/etc/profile.d
substitute ${./command-not-found.sh} \
$out/etc/profile.d/command-not-found.sh \
--replace @nix-locate@ ${nix-locate}/bin/nix-locate \
--replace @tput@ ${pkgs.ncurses}/bin/tput
'';
in {
programs.nix-index = {
enable = true;
package = pkgs.symlinkJoin {
name = "nix-index";
# Don't provide 'bin/nix-index', since the index is updated automatically
# and it is easy to forget that. It can always be run manually with
# 'nix run nixpkgs#nix-index' if necessary.
paths = [ nix-locate command-not-found ];
};
};
}
@izzues
Copy link

izzues commented Jun 30, 2022

Hi, thanks for this!

I'm still a newbie, so I apologize for the dumb question, but how do I use this? I tried the following but got an "infinite recursion" error:

# home.nix
{ config, pkgs, ... }:
let
  nixLocateAuto = pkgs.fetchzip {
    url = "https://gist.github.com/InternetUnexplorer/58e979642102d66f57188764bbf11701/archive/3046ee564fed5b60cee5e5ef39b1837c04cc3aa2.zip";  
    sha256 = "0hz4rpd0g1096sj1bl69kywv1gg14qaq1pk03jifvq9yhcq2i8pc";
  };
in
{
  imports = [
    "${nixLocateAuto}/default.nix"
  ];
 # ...
}

@InternetUnexplorer
Copy link
Author

@izzues I think this is because import resolution happens before everything else, so I don't think you can use pkgs there. One way to get around this would be to use builtins.fetchTarball:

{ config, pkgs, ... }:

let
  nixLocateAuto = builtins.fetchTarball {
    url = "https://gist.github.com/InternetUnexplorer/58e979642102d66f57188764bbf11701/archive/3046ee564fed5b60cee5e5ef39b1837c04cc3aa2.tar.gz";
    sha256 = "0hz4rpd0g1096sj1bl69kywv1gg14qaq1pk03jifvq9yhcq2i8pc";
  };
in {
  imports = [ "${nixLocateAuto}/default.nix" ];
  # ...
}

You could also use builtins.fetchGit of course. And if you're using flakes you could add this gist as an input (with flake = false), but that might be overkill. Hope that helps :)

@izzues
Copy link

izzues commented Jun 30, 2022

Awesome, it worked! Thank you! I had no idea fetchTarball worked for more than just tarballs lol

I also didn't know I could use fetchGit for Gists! Does that mean if I use it with flakes I can then also get updates if you update this Gist without having to manually change the URL and SHA? I still don't know how to do that but I'm gonna do some research.

@InternetUnexplorer
Copy link
Author

It does only work for tarballs; note the file extension :). GitHub allows downloading repositories as both .zip and .tar.gz (and each gist is a repository, which is why fetchGit would also work).

@izzues
Copy link

izzues commented Jun 30, 2022

Huh, I didn't notice that, but I didn't actually change mine and it still works. Just to be sure, I also ran nix store delete /nix/store/67ix3q0s2ycar9lwpq6p780nhjk5jhvd-3046ee564fed5b60cee5e5ef39b1837c04cc3aa2.zip and then rebuilt again.

Apparently fetchTarball really does work with zips! From here:

Just for completion, builtins.fetchTarball serves the same purpose as fetchzip – download an archive and then unpack it into store. Despite the name and docs, both support tarballs and zip files. (Nix uses libarchive, and fetchzip depends on unzip and uses gnutar from stdenv.)

@InternetUnexplorer
Copy link
Author

Oh neat, I didn't know that, I assumed from the name that it didn't. Thanks for letting me know!

@andrevmatos
Copy link

Very cool idea, just inspired me to do some variation which may interest someone: if you use flakes to manage your system (in my case, with flake-utils-plus and its sharedOverlays option, but you can also use nixpkgs.overlays in normal flakes), it's very interesting to have the database tracked by flake.lock and saved in the nix store. Then, home-manager's home.file can be used to create a symlink in your home:

# flake.nix
{
  inputs.nix-index-database.url = "github:Mic92/nix-index-database";
  ...
  sharedOverlays = [
    (final: prev: {
      nix-index-database = final.runCommandLocal "nix-index-database" {} ''
        mkdir -p $out
        ln -s ${nix-index-database.legacyPackages.${prev.system}.database} $out/files
      '';
    })
  ];
}

# in your user's home-manager
{ pkgs, ... }: {
  programs.nix-index.enable = true;
  home.file.".cache/nix-index".source = pkgs.nix-index-database;
}

With the above, your nix flake update will also update references of @Mic92's database, put it in a folder in store and deterministically link to it in your home-manager generation, instead of non-deterministically pulling it with wget. In my case, I didn't mind much the original nix-locate (although I admit yours is prettier), so I just used HM's default programs.nix-index module.

@InternetUnexplorer
Copy link
Author

@andrevmatos I actually like your solution much more! nix-index-database didn't have a flake.nix when I made this, thanks for bringing it to my attention :)

@eyadsibai
Copy link

Very cool idea, just inspired me to do some variation which may interest someone: if you use flakes to manage your system (in my case, with flake-utils-plus and its sharedOverlays option, but you can also use nixpkgs.overlays in normal flakes), it's very interesting to have the database tracked by flake.lock and saved in the nix store. Then, home-manager's home.file can be used to create a symlink in your home:

# flake.nix
{
  inputs.nix-index-database.url = "github:Mic92/nix-index-database";
  ...
  sharedOverlays = [
    (final: prev: {
      nix-index-database = final.runCommandLocal "nix-index-database" {} ''
        mkdir -p $out
        ln -s ${nix-index-database.legacyPackages.${prev.system}.database} $out/files
      '';
    })
  ];
}

# in your user's home-manager
{ pkgs, ... }: {
  programs.nix-index.enable = true;
  home.file.".cache/nix-index".source = pkgs.nix-index-database;
}

With the above, your nix flake update will also update references of @Mic92's database, put it in a folder in store and deterministically link to it in your home-manager generation, instead of non-deterministically pulling it with wget. In my case, I didn't mind much the original nix-locate (although I admit yours is prettier), so I just used HM's default programs.nix-index module.

I am using this solution, but I discovered that the database is not getting updated though I updated the flake.lock

@becknik
Copy link

becknik commented Mar 17, 2024

FYI: there now is a NixOS/home-manager module in the repo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment