A flake for my own script

Here is a handy bash script I wrote called watchlater. There are many more of those tiny helpers that I usually write to prevent repeating myself. For the ease of deployment on multiple machines I also wrote a sort of package managing script called ownscript that cloned the repo and symlinked the script file to a place in my $PATH. Good stuff for the old days of me using ordinary Linux computers.

Now that I am using NixOS, I want to learn how this is done the right way here. From my understanding, the modern way to go is to write a flake for this. There are plenty of posts on the web about how to write flakes, but either I am still misunderstanding or most of them are talking about something else then what I am trying to achieve. I did see this thread, but there as well I think they are not really talking about the same thing. Also the term “legacy” appears a lot within their snippets, which makes me doubt being on the right track there.

What I am trying to achieve:

  • use watchlater … as a regularly available shell command in my NixOS
  • keep the original git repository as it is, i.e. no flake.nix inside there

What I think needs to be done:

  • create a separate place for a flake.nix that defines what needs to be done to have that bash script installed
  • add “install this flake/package” to my NixOS config (not sure if I am using the correct terms here)

However, for reasons of decentralization and the future health of the internet, I do not want to mess with the great nixpkgs repo on Github. From my understanding, it is possible to have multiple nixpkgs repos side by side.

So my approach would be to have e.g. a git repo codeberg.org/mcnesium/nixpkgs with e.g. ./tools/watchlater/flake.nix that I assume it looks like this:

description = "A flake for the watchlater script";

inputs = {
    # declare the original source repo
    watchlater.url = https://codeberg.org/mcnesium/watchlater.git;
};

outputs = { watchlater }:
    # the tricky part: what is needed here to get it done?
    # * clone the repo to whereever nixos wants it to
    # * symlink `watchlater.sh` to my `$PATH` as `watchlater` without extension
    # * install dependencies `yt-dlp` and `ffmpeg`

To include this in my NixOS Setup I would go ahead and add this to the flake.nix like this:

inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    home-manager = { … };
    ownpkgs.url = git+https://codeberg.org:mcnesium/nixpkgs";
};
outputs = inputs@{ self, nixpkgs, home-manager, ownpkgs ... }: { … };

and in my configuration.nix I would write something like

users.users.mcnesium = {
    packages = with pkgs;
      [
          ownpkgs.watchlater
      ];
};

Am I on the right track to achieve what I want? How would the flakes.nix really look like? How would I append this the correct and modern flakes-way to my system?

Why are you trying to avoid adding a flake.nix to the original repo? You can make it work without one in the original repo and create a new repo that just contains the flake.nix but it just complicates the process and the file can just be ignored by non-nix users.

Anyway, I have something similar in my config to make some scripts available on the PATH, you can create the following flake.nix:

{
  description = "Flake for watchlater.sh";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    watchlater.url = "git+https://codeberg.org/mcnesium/watchlater.git/main";
    watchlater.flake = false; # The watchlater repo does not contain a flake.nix
  };

  outputs = { self, nixpkgs, watchlater }: {
    # Create an overlay with a `watchlater` package to use in your NixOS configuration.
    overlays.default = final: prev: {
      watchlater = final.runCommandNoCCLocal "watchlater" {
        nativeBuildInputs = [final.makeWrapper];
      } ''
        # Copy the script into the resulting package.
        install -Dm 755 ${watchlater}/watchlater.sh $out/bin/watchlater

        # Replace #!/usr/bin/env shebang with a path that points to the nix store.
        patchShebangs $out/bin/watchlater.sh

        # Make required programs available to the script.
        wrapProgram $out/bin/watchlater.sh \
          --prefix PATH : ${final.lib.makeBinPath [
            final.curl
            final.yt-dlp
            # More packages that the script depends on...
          ]}
      '';
    };

    # Set up packages to be able to run the script with `nix run <flake-ref>#watchlater`.
    packages = nixpkgs.lib.genAttrs [
      "x86_64-linux"
      # Add other systems where you need this script...
    ] (system: let
      pkgs = import nixpkgs {
        system = sytem;
        overlays = [self.overlays.default];
      };
    in {
      default = self.packages.watchlater;
      watchlater = pkgs.watchlater;
    });
  };
}

Then in your NixOS config you can use the flake as follows (I’m assuming you stored the flake from above on the flake branch of your watchlater repo):

{
  description = "My NixOS configuration";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    watchlater.url = "git+https://codeberg.org/mcnesium/watchlater.git/flake";
  };

  outputs = { self, nixpkgs, watchlater }: {
    nixosConfigurations.myNixosConfig = nixpkgs.lib.nixosConfiguration = {
      modules: [
        ({ pkgs }: {
          nixpkgs.config.overlays = [watchlater.overlays.default];
          users.users.mcnesium.packages = [pkgs.watchlater];
        })
      ];
    };
  };
}

Hi –

The legacy references from above don’t mean what you think they mean.

Also, lots of ways to skin this cat.

You can certainly reuse your existing scripts with a simple “fetchFromGitHub” type call, but it requires you specify the hash (to ensure reproducibility). The problem here is that you’ll have to manually update this every time you update those scripts.

Flakes basically make this process much easier by tracking and versioning your inputs with the lock file.

Another benefit of making it into a flake is that you can run it from any other nix machine directly, eg nix run github:n8henrie/myflake#myoutput -- --myflags.