guttermonk
guttermonk

Reputation: 263

In Nix, how to remove & replace a file from a fetchFromGitHub?

Is there a way to remove a config file from a fetchFromGitHub? I’d like to replace the default config with my own config. So far all of the files from the repo are built and symlinked into ".config/yazelix", but none of the postFetch commands have been ran. Everything builds without any errors.

I've added the following to the home.file section of my "home.nix" file:

      ".config/yazelix".source = pkgs.fetchFromGitHub {
        owner = "luccahuguet";
        repo = "yazelix";
        rev = "1093c18c22d9d9858f4cf5813bd9aa7578660af8";
        hash = "sha256-+6Mq8mR/tF1Z+GyooMz9fWGV57bFhbHXBVBDkHpUMDA="; # build and get the hash from the error
        postFetch = ''
          rm $out/zellij/config.kdl
          cp ./apps/yazelix/config.kdl $out/zellij/config.kdl
        '';
      };

Does anyone have any suggestions to get this to work? Any help is greatly appreciated.

Upvotes: 2

Views: 163

Answers (3)

Frontear
Frontear

Reputation: 1261

Let me follow up with a bit of a deeper explanation as to why this is happening.

When Nix evaluates a derivation, it first checks if there is a corresponding realisation of it in the store already. If one exists, it will skip every remaining step and simply use the path in the store in lieu of building the derivation again. This is because Nix is able to determine that the contents of the derivation itself has not changed, therefore the derivation doesn't need to be updated.

fetchFromGitHub is an even more special derivation besides that. It is what we refer to as a fixed-output derivation, which forces a check of the output contents to a hash to ensure consistency. These are commonly used as downloaders to pull sources from the internet and guarantee consistency via the hash. This hash is set as part of the fetcher's attributes.

Why is this a problem? Well, Nix will use this hash to determine if there is a valid store path of this derivation. If one exists, Nix will immediately use that instead of rebuilding (or in this case refetching) the contents. See why this is a problem? Your postFetch is ignored after every new change unless you also invalidate the hash each time.

Someone pointed out that the issue may be my path in the cp command, since the nix build environment is sandboxed. After correcting my path, as shown below, and updating my hash, everything was working as it should.

Too early to celebrate! You've only managed to get it working thanks to invalidating the hash. It had nothing to do with switching your path here. Try pushing a new revision update to the repo or even changing your config and you'll see that neither action affects the derivation on your system at all. Nix can find the old derivation because the hash hasn't been invalidated, so it immediately uses it instead.

Furthermore, you do not need a fancy mkDerivation wrapper as suggested in another answer. It's perfectly acceptable, but unnecessary. The only important thing is correctly invalidating the hash.

A simpler derivation you can write (assuming you aren't updating this GitHub repo that much on its own), is to use a bare bones runCommandNoCC:

runCommandNoCC "my-config" {
  src = fetchFromGitHub { ... };
} ''
  mkdir -p $out/zellij

  cp ${./path/to/your/config.kdl} $out/zellij/config.kdl
  cp --no-clobber --recursive --target $out $src/*
''

This little derivation will automatically determine that your file has changed during evaluation and will automatically replace it each time. You can FURTHER optimize this by using a symlinkJoin to avoid copying so many files everywhere (instead it uses symlinks), which you can do at your own discretion.

Upvotes: 1

guttermonk
guttermonk

Reputation: 263

Someone pointed out that the issue may be my path in the cp command, since the nix build environment is sandboxed. After correcting my path, as shown below, and updating my hash, everything was working as it should.

  ".config/yazelix".source = pkgs.fetchFromGitHub {
    owner = "luccahuguet";
    repo = "yazelix";
    rev = "1093c18c22d9d9858f4cf5813bd9aa7578660af8";
    hash = ""; # build and get the hash from the error
    postFetch = ''
      rm $out/zellij/config.kdl
      cp ${./apps/yazelix/config.kdl} $out/zellij/config.kdl
    '';
  };

Passing the relative path/file in as a variable, in the postFetch, allows it to be used in the sandboxed build environment. There may be other ways of doing this, but this technique is working and is fairly elegant.

Upvotes: 0

Loïc Reynier
Loïc Reynier

Reputation: 385

The postFetch snippet you provided should work fine.

I think the issue is that source = pkgs.fetchFromGithub looks for the hash to update the store entry. If you have built a first generation without postFetch, Home Manager won't rebuild automatically the store entry after you add or edit the postFetch phase.

Manual method

To force the update, you should delete the store entry and rebuild your home configuration following these steps:

  1. Remove ".config/yazelix".source and build a new home configuration generation. This is needed to remove the dependency on the Nix store, otherwise you won't be able to delete the entry.
  2. Remove the previous home configuration generation with home-manager remove-generation <id>. You can find the id with home-manager generations.
  3. Delete the store entry with nix-store --delete <path>. You can find the path with ls -dl ~/.config/yazelix.
  4. Add back ".config/yazelix".source and build a new home configuration generation

Automatic method

The manual method is not future-proof as you'll need to apply it again each time you edit config.kdl. An alternative is to create a derivation:

file = let
  myConfig = pkgs.stdenv.mkDerivation {
    name = "custom-yazelix-config";
    src = pkgs.fetchFromGitHub {
      owner = "luccahuguet";
      repo = "yazelix";
      rev = "1093c18c22d9d9858f4cf5813bd9aa7578660af8";
      hash = "sha256-+6Mq8mR/tF1Z+GyooMz9fWGV57bFhbHXBVBDkHpUMDA=";
    };
    installPhase = ''
      mkdir -p $out/zellij
      cp ${self}/apps/yazelix/config.kdl $out/zellij/config.kdl
    '';
  };
in {
  ".config/yazelix".source = myConfig;
};

Here ${self} is the path of the flake providing the home configuration.

Upvotes: 2

Related Questions