Personal Linux dotfiles and utilities, bundled as a Nix flake with an installer and a maintenance front-end. Works on both NixOS and non-NixOS hosts via home-manager.
The config is kept self-contained: the repo is symlinked to ~/.stubbe, so
removing the repo removes the settings with it.
One file per aspect, and each aspect owns everything about itself — its
packages, its config files, its systemd units, its activation steps, and both
its NixOS and its home-manager half. There is no packages/ bag, no files/
bag and no activation/ bag to keep in sync: to change how mail works you edit
modules/mail.nix, and that is the whole story.
.stubbe/
├── flake.nix # inputs; outputs are `import-tree ./modules`
├── modules/
│ ├── core/ # the foundation (see CONVENTIONS below)
│ ├── hosts/ # host definitions, one file each
│ ├── dev/ # flake checks, lint, `nix fmt`
│ ├── scripts/ # the `hm` front-end, launchers, clip, ISO burner
│ ├── ai/, browsers/ # aspects big enough to want a directory
│ └── <aspect>.nix # mail, shell, hyprland, lys-shell, power, storage, …
├── bin/ # ONLY the pre-Nix bootstraps (stb-install*)
├── src/ # the few real files: nvim + hyprland live-edit
│ # trees, aerc stylesets, wallpapers
├── certs/ # the srv root CA (public cert); seeded into the
│ # system trust store at build time
└── secrets/ # sops-encrypted, keyed per machine in .sops.yaml
Every config file and script is inline Nix inside its aspect module. Scripts
become PATH bins through pkgs.stubbe.bashApp (build-time shellcheck),
pkgs.stubbe.zshApp (build-time zsh -n), or pkgs.stubbe.shellScript /
shellScriptBin for scripts that must keep plain (non-set -euo pipefail)
semantics. bin/stb-install runs from the checkout — it bootstraps Nix and
home-manager on a fresh host.
-
One file, one aspect, both classes. Every file under
modules/registers underflake.modules.homeManager.<aspect>and/orflake.modules.nixos.<aspect>. Values shared between the two halves are let-bound at the top of the file, which is what stops a NixOS module and its non-NixOS activation from drifting apart. Hosts import a whole class withbuiltins.attrValues; nothing references a module by name. -
No opt-outs. Every
.nixfile undermodules/is auto-loaded by import-tree and must be a flake-parts module. There are no underscore-prefixed exceptions. -
Three layers of shared code, and no fourth.
stubbe.lib.*(modules/core/lib/) — pure data and pure functions:colors,theme,newtabUrl,cache, thehmcommand catalogue,managedBy. Needs nothing; mirrored asflake.lib.pkgs.stubbe.*(modules/core/pkgs/) — every builder that needspkgs:secret,bashApp,zshApp,shellScript,gen,gl,check, and thesetup.*script fragments. Re-exports all ofstubbe.lib, and is reachable from any module of any class because it rides onpkgs.config.stubbe.*— options for anything derived from the evaluated configuration:paths,gfx,setup,mutable,userFeatures.
There is no
specialArgsand noextraSpecialArgsanywhere. A module that needs a flake input resolves it at flake-parts level, whereinputsis already in scope. This is what makes every aspect movable between the two targets without rewiring. -
Feature gates and the platform split. Behaviour toggles are
features.*options (modules/core/features.nix), gated withlib.mkIf config.features.<x>. NixOS aspects gate onconfig.stubbe.userFeatures.<x>, the same flags resolved once from the primary user. On NixOS the declarative half owns system state; on any other distro the same aspect'sstubbe.setup.<name>.privilegedactivation writes the equivalent files into/etc, and gates itself off on NixOS. -
Activation steps.
stubbe.setup.<name>(modules/core/setup.nix) is the one way to run imperative work at switch time. The attribute name is the DAG node name;privileged = trueadds the shared sudo-prompt scaffolding, a content-hash lock so an unchanged step costs nothing, and the NixOS gate. -
Mutable files.
xdg.configFile/home.file— read-only store symlinks — are the default.stubbe.mutable.<path>(modules/core/mutable.nix) is for the two cases that cannot be:link(point at the live checkout so an edit needs no rebuild) andcopy(the app rewrites the file, so re-assert ours each switch). -
Everything is Nix. Config is structured Nix data rendered through
lib.generators/pkgs.formatswhere a format exists (INI, TOML, YAML, JSON), and inline strings where none does — so the Catppuccin palette exists once, inmodules/core/lib/palette.nix, and flows into every themed surface by interpolation. The only real files left undersrc/are the ones that must be files: the nvim and hyprland live-edit trees (stubbe.mutablelink), binary wallpapers, and the sops secrets.
git clone --depth 1 https://github.com/stubbedev/dotfiles.git
cd dotfiles && ./bin/stb-installThe installer asks once for confirmation, then does everything. Day-to-day rebuilds then run
through hm.
The flake lives at the repo root. Apply with:
hm switch --flake "path:$HOME/.stubbe"Or directly with home-manager:
home-manager switch --flake .#stubbeBuild a bootable installer ISO that mirrors the stubbe-nixos host:
nix build .#installer-iso --impure
# Output: ./result/iso/*.iso — flash to a USB stick.Boot the live USB on the target, log in as root, and run:
stb-install-nixosIt partitions the disks as a btrfs volume, runs nixos-install, and clones the
repo to /mnt/etc/nixos/dotfiles.
Optional hardening is available behind per-host flags in the host file
(modules/hosts/stubbe-nixos.nix):
- Secure Boot (
host.secureBoot) — lanzaboote signed bootloader; enroll keys withsbctlfirst. - Impermanence (
host.impermanent) — wipes/on every boot, keeping only paths declared inmodules/impermanence.nix.
Both require setup steps before flipping the flag — see the comments in the host file and module.
