Setup
The history of how I've gotten to this point is winding and many decades long. But the end result is that I use two major tools for setting up my projects in a consistent manner: Direnv and Nix Flakes.
The main reason is history. I have projects that are twenty years old but they are based on languages that never stop changing. In some cases, such as the highly fluid NPM ecosystem, being off by a month can require days of refactoring to handle the current state of deprecations, shifting packages, and trying to get everything playing with each other again.
Nix Flakes help keep me “pinned” to specific versions and reduces the amount of refactoring I have to do to jump into a project, do a few changes, and finish up.
Flakes
Since flakes give me the consistency I need, that means many of my more recent projects have a flake.nix
and a flake.lock
associated with them. These will have a devShell
which makes sure the programs to use the project are available.
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-24.11";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils, mfgames-project-setup, mfgames-writing-setup }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShell = pkgs.mkShell {
packages = [ pkgs.scc ];
};
});
}
I try to avoid using the “unstable” branch for NixOS but sometimes that isn't avoidable.
mfgames-project-setup
To simplify my common project layout, I created a setup flake that does some initial configuration including setting up commit linting (since I useconventional commits), formatting, and set up some of the boilerplate files.
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-24.11";
flake-utils.url = "github:numtide/flake-utils";
mfgames-project-setup.url = "git+https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git";
};
outputs = { self, nixpkgs, flake-utils, mfgames-project-setup }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
project-config = mfgames-project-setup.lib.mkConfig {
inherit system;
pkgs = nixpkgs.legacyPackages.${system};
prettier.proseWrap = "never";
};
in
{
devShell = pkgs.mkShell {
packages = [ ]
++ project-config.packages;
shellHook = project-config.shellHook;
};
});
}
I'm pretty proud of that one, since it basically does 90% of my setup when starting a new project. There is also a second setup flake that sets up my writing projects including getting the right tools installed.
Direnv
To make it easier to get into the environment set up above, I use direnv
. My shell is set up to automatically include this, and it sets up paths.
use flake || use nix
dotenv_if_exists
PATH_add node_modules/.bin # NPM
PATH_add target/debug # Rust
PATH_add .local/bin
The use flake
command above will automatically enter the environment configured by flake.nix
and makes sure I have access to all the tools I need for a given project.
Environment
There are two places I set up environment variables.
For persisted ones, I put them into the .envrc
file which is checked into the code. This is used by projects like Fiss to set the local cache file to the one inside the repository and to use the project-local configuration instead of using (and possibly corrupting) my day-to-day one.
For ones not checked in, I use .env
to set those variables. This is where I put things like AWS authentication or Forgejo tokens. My Justfile is set up to avoid removing those when I do a just clean
and it also loads them using set dotenv-load
.