firn is a typed front-end for NixOS and nix-darwin — it catches option
typos and type errors at the source line, before nixos-rebuild ever runs.
Keeps the standard NixOS module model, swaps in a small Racket DSL (beagle/nix) for authoring, adds pre-eval diagnostics that catch option typos and type errors at the source line — typically cutting edit/validate loops from ~30 seconds to ~5 seconds.
$ firn rebuild
modules/printing/default.bnix:6:7: unknown option services.pipwire.alsa.enable
did you mean: services.pipewire.alsa.enable or services.pipewire.pulse.enable?
modules/foo/default.bnix:9:34: type mismatch at services.openssh.enable:
expected bool, got string
hosts/laptop/configuration.bnix:11:47: type mismatch at boot.loader.systemd-boot.consoleMode:
"atuo" not in enum {…} — did you mean "auto"?
file:line:col precision on the value, with did-you-mean suggestions,
before nixos-rebuild runs. That's the whole pitch — the validator
lives in beagle.
This repository is two things at once: the firn framework, and the
author's real NixOS + nix-darwin config built on it. To use firn for
your own machines, start from template/. The full
repo (hosts/whiterabbit/, ~188 modules) is here as a study
reference, not as something to fork wholesale.
nix flake init -t github:tompassarelli/firn # drops template/ in cwd
git clone https://github.com/tompassarelli/beagle ../beagle # compiler + validator
cp /etc/nixos/hardware-configuration.nix .
# edit hosts/my-machine/configuration.bnix and hosts/my-machine/enabled-tags.bnix
firn repo build && nixos-rebuild switch --flake .#my-machineBEAGLE_PATH overrides the sibling-clone location. macOS works the
same way via lib.mkDarwinSystem and a darwinConfigurations entry —
firn rebuild detects Darwin and dispatches to darwin-rebuild.
firn rebuild # build + validate + switch (current host)
firn repo validate # static check the .bnix tree
firn host impact # preview what would build
firn repo diff # diff regenerated .nix vs committed
firn tag enable <t> # enable a tag
firn tag disable <t> # disable a tagCommands use a <node> <edge> [<leaf>] triple. Leaves default to the current
host or all where the edge defines that default. firn rebuild [host] is the
canonical build-and-switch shortcut; run firn with no args for the full grid
or firn <node> for one entity's edges.
sops-nix: encrypted secrets/*.yaml are
committed, the private age key stays machine-local, .sops.yaml lists the
public recipients. The awscli module is opt-in, so the config builds clean
without it.
→ docs/secrets.md — key layout + bring-your-own-key fork recipe.
Module = atom (one package/service, modules/<name>/default.bnix).
Tags = composition (a module declares :tags; hosts select tags; the
resolver unions memberships minus a per-host disabled list).
Host = leaf (configuration.bnix + enabled-tags.bnix). .bnix is the
source, .nix is generated — both committed, edit the .bnix.
→ docs/architecture.md — resolver chain, repo layout, module auto-discovery.
- docs/TAGS.md — tag-driven composition model, resolution algorithm, worked examples
- tompassarelli/beagle — the DSL itself: compiler, validator, schema extractor, migration tool
- The
firnCLI is self-documenting:firn(full grid),firn <node>(one entity),firn schema explain <path>(schema introspection)
- One sibling-repo dependency (
../beagle). - Two-language requirement (Racket s-expressions + Nix concepts).
- Two artifacts per file (
.bnix+.nix, both committed). - Schema cache is host-specific and dated; regenerate after flake input changes.
- DSL ceiling — escape hatch (
raw-file, hand-written.nix,nix-ident) covers the gaps.
doomemacs/doomemacs · basecamp/omarchy · fufexan/dotfiles · redyf/nixdots · eduardofuncao/nixferatu
Firn is dual-licensed under either the MIT License or the
Apache License, Version 2.0, at your option
(MIT OR Apache-2.0).