I wanted a way to declare what I need without adopting a complex system like Nix or Ansible just for a single laptop. The result was a plain old Makefile.
I wrote a short post on using Make (along with a tiny bash script and fzf) to create a searchable, single-command registry for all your local dev tools. It’s not a new framework or a heavy tool—just a simple way to organize the package managers we already use.
If you're tired of losing track of your local environment, you might find it useful.
Your solution is akin to putting your dotfiles in the code repo, which is going to cause issues with languages with poor version compatibility (such as node and python) when switching between old projects.
Also, bold of you to assume developers know make and bash just because they’re using Linux!
This means that getting a project in shape for development on a new system looks like this:
- clone project
- `mise run setup`
I have zero dev tools on my host, projects are 100% self-contained.
Pure bliss.
See https://github.com/dx-tooling/sitebuilder-webapp for an example.
When installing tools, or via mise.toml, you can define version ranges with the precision you'd like - "3" / "3.1" / "3.1.2".
For example there's also the GitHub backend which lets you install binaries from releases, no plugin needed at all.
But these days, I just tell codex to install things for me. I basically use it as a universal package manager. It's more reliable honestly than trying to keep up to date with "what's the current recommended way to install this package?"
I also have it keep a list of packages I have installed, which is synced to GitHub every time the list changes.
target:
llm command "Install X for me."
(PS. I don't even know if I'm joking anymore)Weeks sounds way more accurate than 1-2 hours.
1. Install nix / determinate nix
2. Tell your favorite llm to set up https://github.com/nix-darwin/nix-darwin with home manager if you are on mac, or just home manager if you are on linux
3. Review the code and ask for clarifications
You'll have a set up in 20 minutes.
{ pkgs, ...}: {
home.packages = with pkgs; [ neovim lazygit ];
}With home-manager I have the same packages, same versions, same configuration, across macOS, NixOS, Amazon Linux, Debian/Ubuntu... That made me completely abandon ansible to manage my homelab/vms.
Also adding flake.nix+direnv on a per project basis is just magical; I don't want to think how much time I would have wasted otherwise battling library versioning, linking failures, etc.
Make is generic. Nix is not.
Before I even look at the actual code I already know that it is something I can use immediately on my existing system, no matter what that happens to be, right now, without changing anything else.
It doesn't matter how great nix is because it's not alpine or xubuntu or suse or freebsd or sco osr5 or solaris or cygwin, it's nix.
Even if you're only talking about nix the package manager, or nix the language, and not nix the os, it actually still applies because Make is everywhere and nix is not.
Even if this thing has bash-isms and gnumake-isms, I bet with minimal grief I can still use it on a Xenix system that doesn't even have a compiler (so no building nix) but does have ksh93 and make, even without leaning on the old versions of actual gnu make and bash that do exist.
Hard disagree on this one. It's a series of makefiles that depend on apt (or whatever pacman you choose), so for any heterogeneous environment it's going to constantly be uphill battle to keep working in terms of package naming, existence of dependencies, etc. You'd find yourself reinventing Ansible, but worse.
> It doesn't matter how great nix is because it's not alpine or xubuntu or suse or freebsd or sco osr5 or solaris or cygwin, it's nix.
Nix runs fine on most (all?) modern Linux distros, macOS, even WSL, and there are workarounds to make it run on BSD, though I admittedly haven't tested those.
> Even if this thing has bash-isms and gnumake-isms, I bet with minimal grief I can still use it on a Xenix system that doesn't even have a compiler (so no building nix) but does have ksh93 and make, even without leaning on the old versions of actual gnu make and bash that do exist.
Use it on Xenix (which last shipped in 1991) to do what? The package management was tarballs and compiling. Instead of reinventing Ansible, you'd be reinventing pkgsrc. Not sure what your point here is.
If you want this makefile way, use Justfiles,which are modernized Makefiles so you dont have 30 years of cruft and such being injected into it.
But also at the end of the day, Mise exists and is directly targetted for this.
I personally use Brew bundle files + Dotter for my home/tools management. Brew bundles also support Flatpaks these days and i can install those too.
What's the use case for homebrew on Linux these days? Most distributions have their own package manager, which you almost always end up using anyways, so already you're adding an extra package manager. Besides that, most of the community isn't using homebrew, so clearly won't have "more" packages, and the packages it'll have will be less reviewed than the ones in your distribution. I can't really see any point to use homebrew on Linux except "I used to use it on macOS", which doesn't feel that strong of a use case really.
Sooo, i dont have a system package manager to use to add more packages, not without building my own image ontop of Bluefin/Bazzite.
Also, all the packages on Brew are fairly well tested, while mostly on OSX, they officially release Linux prebuilts for Linux and get tested equally. Brew has been around for ages.
And I havent used MacOS for 8-9 years, and only for a small stint. Not long enough for it to do things.
Also per official stats: https://formulae.brew.sh/analytics/os-version/90d/ Ubuntu makes up ~20% of brew usage, and the Universal Blue family is about 2% and growing.
There is absolutely a usecase for it and its just as good if not better, as most tools are more likely to be statically built, and you donthave a giant dependency mess and other nonsense to deal with. Its cleaner.
On an immutable distro, its a lot of Flatpak, AppImage, and Brew/Mise/etc. Layering packages is greatly discouraged and as the ecosystem moves towards Bootc images over OSTREE ones, the option will go away entirely. You either build a custom image with yoru custom stuff layered on yourself (there are templates and Github CI stuff to help with it.) or you use other package managers.
Also another win is weith Brew, i can reproduced my tools and environment quickly and dont have to deal with Distro quirks. Brew works the same on almost every distro, same pathings, same behavior, and even offers the Brewfiles to let me specify my setup.
I recently switched jobs and had my work setup installed and created within mins of booting into a fresh install and I was working shortly after.
> Docker/containers solve isolation, not tool installation. You do not want to run your editor, terminal, and CLI tools inside containers.
I'm not in agreement here. You can have a Dockerfile in which all tools get installed. You build it, and tag it with, let's say `proj-builder`.
Then you can run commands with a mounted volume like `docker run --volume $(pwd):/sources <tool call>`. And alias it.
I made something similar but with Ansible wrapped as an uv script. What I like about ansible is that it's higher level so I'm able to do complex modifications to my machine without having to write them myself and handle the errors myself because the community behind the tasks have already done it. The idempotency of ansible out of the box is also very nice.
Here is my ansible/uv-script project if someone is interested: https://camilo.matajira.com/?p=591
> Every developer on Linux already knows both.
I've been developing on Linux for over 10 years and I don't. It's like exiting vim: whenever I want to do anything beyond running a command or basic variable use, I have to go lookup how to do it online. Every time.
With tooling for deployment I prefer to heed an adaptation of Greenspun's Tenth Rule. Neither Guix nor Nix are really all that "complex" from a user's perspective.
The main difference is I initially only needed a mechanism to check if my "Manually-Installed or Source -Compiled" (MISC) packages have updates, but now it also supports install/upgrading too.
In other words, things I am forced to do by hand outside of a package manager, I now only do by hand once, save it as an 'install' script, and then incorporate it into this system for future use and to check for updates. Pretty happy with it.
Configuration is in scheme (guile) so that may be a turn off though.
I do almost all of it work in the terminal, so I had already been using chezmoi to manage my dotfiles for a few years. Eventually I added an Ansible bootstrapping playbook that runs whenever I setup a new environment to install and configure whatever I like.
I’m already living & breathing Ansible most days so it wasn’t a heavy lift, but it’s a pretty flexible approach that doesn’t bind me to any specific type of package manager or distro.
Something like:
go get -tool github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.4
And then you can list tools : go list -f '{{.Tool}}' all
ANd run them: go tool staticcheck ./...I'd love to see something like a "discover" option that attempts to scan what things I have added to assist me in building the make for the next time.
pixi init && pixi add wget
And youre ready to go, everything confined to the venv within the directory
Therefore my middle ground is devbox.
It is like python vietualenv but backed by Nix. So I have a devbox.json file to define packages and devbox will do the Nix part for me.
I am getting a MacOS and Linux setup from this for aarch64 and x86
Kudos to you!