/flake-parts-builder

Nix flakes interactive template builder based on flake-parts written in Rust. [maintainer=@tsandrini]

Primary LanguageRustMIT LicenseMIT

flake-parts-builder

flake check FlakeHub cachix flake.lock update

About 📝

Building a new flake-parts project? Need a template with all the necessary boilerplate, but none perfectly fits your needs? Just choose the parts that you need and build your own!

nix run github:tsandrini/flake-parts-builder -- init -p +github,+nixos,treefmt myNewProject

Templates defined to be used with nix flake init -t typically suffer from the case of being static and too simple. They usually address only one specific thing or problem domain (eg. devenv, rust, flake-parts, dotfiles, ...) which makes the end user quickly start running into issues when trying to combine said domains since real life flake projects rarely require only one such domain.

And this is what flake-parts-builder solves! It serves as a dynamic extension to nix flake init -t, nothing more, nothing less! So let's forget about nix flake init -t and embrace nix run instead 😎


Okay, but what exactly does it do then?

  • flake-parts-builder init - initialize a new project with all your required parts
  • flake-parts-builder add - add new parts to an already existing flake-parts project
  • flake-parts-builder list - list all currently available flake-parts to be used with the list and add subcommands

Installation 🤖

Disclaimer: flake-parts-builder is built on top of nix flakes and flake-parts hence why familiarity with flakes is a necessity. The builder also currently runs in flakes mode only and uses flakes to parse flake-parts stores. The following "experimental" features are then a forced requirement --experimental-features 'nix-command flakes'.

NOTE: if enough people will be using this project I don't have any issues with pushing it into upstream nixpkgs.

Nix CLI

nix profile install github:tsandrini/flake-parts-builder

NixOS

{
  inputs.flake-parts-builder.url = "github:tsandrini/flake-parts-builder";

  outputs = { self, nixpkgs, flake-parts-builder }: {
    # change `yourhostname` to your actual hostname
    nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem {
      # change to your system:
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
        ({ system, ... }: {
           environment.systemPackages = [ flake-parts-builder.packages.${system}.default ];
        })
      ];
    };
  };
}

Binary cache 💾

flake-parts-builder is written in Rust with minimal dependencies to ensure safety and consistency, however, there is also a binary cache available if you'd like to skip the build process.

  nixConfig = {
    extra-substituters = [
      "https://tsandrini.cachix.org"
    ];
    extra-trusted-public-keys = [
      "tsandrini.cachix.org-1:t0AzIUglIqwiY+vz/WRWXrOkDZN8TwY3gk+n+UDt4gw="
    ];
  };

Available parts 📂

You can list all of the available parts with the flake-parts-builder list subcommand, which goes through all of the flake-parts stores passed via the -I or --include flag. Here is the current output the list subcommand running on only the base parts provided by this flake (note that you can disable the base parts using --disable-base if you wish so)

flake-parts-builder list
 # github:tsandrini/flake-parts-builder#flake-parts
  - +github: (Collection) GitHub related parts
  - +home-manager: (Collection) Home-manager related parts.
  - +nixos: (Collection) NixOS related parts.
  - agenix: Bindings for the agenix secrets manager with prepared NixOS/HM modules ready to be used in your configurations.
  - devenv: Flake bindings for the `github:cachix/devenv` development environment.
  - flake-root: Provides `config.flake-root` variable pointing to the root of the flake project.
  - gh-actions-check: Adds a simple `nix flake check` GitHub action workflow.
  - gh-actions-flake-update: Adds the periodic `DeterminateSystems/update-flake-lock` GitHub action workflow.
  - gh-actions-flakehub: Adds the push to FlakeHub GitHub action workflow.
  - gh-actions-pages: Adds a GitHub action that runs `nix build .#pages` and deploys the result to GitHub pages.
  - gh-templates-PR: Adds a basic GitHub pull request template.
  - gh-templates-issues: Adds basic bug/feature GitHub issue templates.
  - gitlab-ci-check: Adds a simple `nix flake check` to your GitLab CI/CD pipeline.
  - hm-homes: Template for your HM homes and a handy generator for you `homeManagerConfiguration` calls.
  - hm-modules: Basic template for custom home-manager modules.
  - lib: Basic template for custom nix library functions.
  - nix-topology: Adds bindings for the `github:oddlama/nix-topology` project to generate graphs of your networks.
  - nixos-hosts: Template for your NixOS hosts and a handy generator for `lib.nixosSystem` calls.
  - nixos-modules: Basic template for custom NixOS modules.
  - overlays: Basic template for custom nixpkgs overlays.
  - pkgs: Basic template for custom nix packages (ie derivations).
  - pre-commit-hooks: Bindings for pre-commit-hooks.nix and a simple pre-commit-hook template.
  - process-compose-flake: Bindings for process-compose-flake and a simple process-compose template.
  - shells: Basic template for custom nix devshells (ie. `mkShell` calls) with potential bindings to other parts.
  - systems: Sets up the default `systems` of flake-parts using `github:nix-systems/default`.
  - treefmt: Bindings for the treefmt formatter and a basic treefmt configuration.

 # github:tsandrini/flake-parts-builder#flake-parts-bootstrap
  - _bootstrap: (Required) Minimal set of functions used to bootstrap your flake-parts project.

Using your own parts 👨‍💻👩‍💻

flake-parts-builder was designed from the ground up with extensibility in mind. To be able to use local parts, remote parts and cache parts in an easy manner the CLI accepts additional flake-parts stores via the -I or --include flag as flake derivation outputs. Meaning that you can run

flake-parts-builder init -I ./myDir#flake-parts -p shells,pkgs,my-custom-part myNewProject

or even remote parts stores

flake-parts-builder init -I github:org/my-flake-project#flake-parts -p shells,my-custom-remote-part myNewProject

Thanks to the wonders of nix, the flake-parts stores will be resolved & fetched only once and on successive calls, they will be copied directly from you local /nix/store cache.

Custom flake-parts-stores

A flake-part store is any derivation that has flake-parts located at $out/flake-parts, so for example the following snippet

stdenv.mkDerivation {
  name = "my-custom-flake-parts";
  version = "1.0.0";
  src = ./flake-parts;

  dontConfigure = true;
  dontBuild = true;
  dontCheck = true;

  installPhase = ''
    mkdir -p $out/flake-parts
    cp -rv $src/* $out/flake-parts
  '';
}

Custom flake-parts

A flake-part is any folder with a meta.nix file at its root containing an attrset with the following structure.

{
  description = "Flake bindings for the cachix/devenv development environment.";

  inputs = {
    devenv.url = "github:cachix/devenv"
    # ....
  };
  dependencies = [ ];
  conflicts = [ "shells" ];
  extraTrustedPublicKeys = [ "https://devenv.cachix.org" ];
  extraSubstituters = [ "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=" ];
}
  • description: a simple description of the provided parts printed when running flake-parts-builder list
  • inputs: flake inputs that will be recursively merged from all required parts and pasted into the final flake.nix file
  • dependencies: with this you can add any additional required parts for the initialization/addition, this can be either a full flake uri (eg. github:org/my-flake-project#flake-parts/my-custom-flake-part) or a local reference to some other part included in the same flake-parts store (eg. my-custom-part)
  • conflicts: a specification of other potentially conflicting parts (for example when handling the same functionality, like devenv and shells) that should abort the process in case of found conflict, note that you can force the initialization/addition even in case of conflict with the --ignore-conflicts
  • extraTrustedPublicKeys: merged with all of the required parts and pasted into the final flake.nix, for security purposes they are all commented out
  • extraSubstituters: merged with all of the required parts and pasted into the final flake.nix, for security purposes they are all commented out

Additional questions, issues 🗣️

How can I use a custom version of the nix binary?

If installed via the nix package manager, flake-parts-builder will use an isolated version of pkgs.nixVersions.stable with --extra-experimental-features 'nix-command flakes' enabled. However, if you'd like to use a custom version instead, simply pass it via $NIX_BIN_PATH, for example

NIX_BIN_PATH=/bin/patched-nix flake-parts-builder init -p +home-manager,shells myNewProject

Why not use flake.templates instead?

The flake.templates flake output is a static property by design that needs to point to a fixed path with fixed content known ahead of time, which makes it heavily impractical for any kind of dynamic evaluation. One could, given the set of parts, prepare all of the possible combination of templates with some patching script and directly update the source code of flake.nix, however ... At the time of this writing there are currently $27+1$ flake parts provided by this flake in the base collection of parts, which would result in

$$2^{28} - 1 = 268435455$$

total combinations of templates and with an average part size of $8.59 \pm 2.60$ KB this would result in $2.14$ total terabytes of data with just one part per template. 💀

I hope this is enough of an answer.

Can't we just stuff this functionality into flakeModules?

I totally agree there is a fine line between a reusable piece of functionality and boilerplate template code and I personally can't think of a general enough definition that would discern them and also be somehow useful. However, I do believe there is a practical, clearly visible difference between them that most programmers can just simply look and see, let's for example take .... devenv/dev.nix or nix-topology/topology.nix or even flake-check.yml, you can clearly "see" that this isn't a good candidate for a flakeModule, they are too specific, they typically represent the end user options of some existing flakeModules. Wrapping this code into another layer of modularity doesn't make sense, since this is meant to be a piece of configuration code.

Help! I'm experiencing an XYZ bug!

I'm sorry for the inconvenience, please run whatever is producing said bug with these RUST_LOG=debug RUST_BACKTRACE=full environment variables, for example

RUST_LOG=debug RUST_BACKTRACE=full flake-parts-builder add shells ./myProject

and paste the output into a new bug issue. Thanks! ❤️