Day 11 — flake-parts, Checks, Overlays & Gate II
Day 11 — flake-parts, Checks, Overlays & Gate II
Day 29 — flake-parts or discipline
Stage III · ~4h
Goal: Choose a house style for multi-output flakes—either adopt flake-parts to reduce boilerplate, or enforce a hand-rolled structure with equal consistency—and apply it to your Stage III flake.
There is no moral victory in either choice. There is only consistency. Mixing half flake-parts and half ad-hoc outputs = args: … is the failure mode.
Why this day exists
By now your flake may export:
nixosConfigurations.*
devShells.*(host-level tooling)
- Soon:
checks.*, maybepackages.*,formatter
Raw outputs functions grow nested let blocks and copy-pasted system lists. Two responses exist in the ecosystem:
| Style | Idea |
|---|---|
| flake-parts | Framework: modules for flakes; per-system helper; community modules |
| Hand-rolled discipline | Small local helpers (forAllSystems), strict file layout, no new dependency |
Today you pick one and document it.
Theory 1 — Pain that motivates structure
Typical messy outputs:
outputs = { self, nixpkgs, home-manager, ... }@inputs:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in {
nixosConfigurations.lab = /* ... */;
devShells.${system}.default = /* ... */;
# forgot aarch64; forgot checks; formatter missing
};Problems:
- Single-system assumptions
- Repeated pkgs construction
- Hard to share patterns across repos
- Easy to forget an output class
Theory 2 — Hand-rolled discipline (no flake-parts)
Pattern used widely:
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
# ...
outputs = { self, nixpkgs, ... }@inputs:
let
systems = [ "x86_64-linux" "aarch64-linux" ];
forAllSystems = nixpkgs.lib.genAttrs systems;
pkgsFor = system: import nixpkgs {
inherit system;
# overlays = [ self.overlays.default ];
};
in {
nixosConfigurations.lab = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [ ./hosts/lab /* hm module wiring */ ];
};
devShells = forAllSystems (system:
let pkgs = pkgsFor system; in {
default = pkgs.mkShell {
packages = with pkgs; [ nixpkgs-fmt nil ];
};
});
formatter = forAllSystems (system: (pkgsFor system).nixpkgs-fmt);
};
}House rules for hand-rolled
- One
systemslist
- One
pkgsFor/ overlay injection point
- Host modules stay under
hosts/; flake stays wiring
- New output types get a comment in
docs/flake-style.md
- No deep logic in
flake.nix—call into./libor./flake/if needed
Theory 3 — flake-parts approach
flake-parts turns outputs into a module system for flakes.
{
description = "lab with flake-parts";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
flake-parts.url = "github:hercules-ci/flake-parts";
home-manager.url = "github:nix-community/home-manager/release-26.05";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" "aarch64-linux" ];
perSystem = { pkgs, system, ... }: {
devShells.default = pkgs.mkShell {
packages = with pkgs; [ nixpkgs-fmt nil ];
};
formatter = pkgs.nixpkgs-fmt;
};
flake = {
nixosConfigurations.lab = inputs.nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
./hosts/lab
inputs.home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.alice = import ./homes/alice;
}
];
};
};
};
}| Concept | Role |
|---|---|
perSystem |
Outputs that vary by system (packages, devShells, checks, formatter) |
flake = { ... } |
Top-level flake attrs like nixosConfigurations |
| Modules | You can split flake-parts config across files |
Costs
- Another input to pin and learn
- Stack traces include framework layers
- Overkill for a single
nixosConfigurationsonly—but you are past that
Theory 4 — Decision rubric
| Choose flake-parts if… | Choose hand-rolled if… |
|---|---|
| You expect many per-system outputs | You want minimal dependencies |
| You like module-shaped config everywhere | You already understand raw flakes well |
| You will copy patterns across many repos | One lab flake is enough for months |
| You accept framework upgrades | You prefer total local control |
Book stance: either is valid. Capstone graders care that you have a style, not which logo you use.
Theory 5 — What not to do
- Rewrite layout (Day 23) and adopt flake-parts in the same panicked hour without rebuild
- Import flake-parts and still paste a second full
outputstree beside it
- Put NixOS module logic inside
perSystemincorrectly (hosts are usually underflake.nixosConfigurations)
- Enable every community flake-parts module on day one
Theory 6 — Formatter as a style signal
Regardless of framework, expose a formatter:
nix fmtWith formatter output set, this becomes a team habit before Day 30’s checks.
Worked example — Split hand-rolled helpers
# lib/flake-utils.nix
{ nixpkgs }:
rec {
systems = [ "x86_64-linux" "aarch64-linux" ];
forAllSystems = nixpkgs.lib.genAttrs systems;
pkgsFor = system: import nixpkgs { inherit system; };
}# flake.nix uses:
# let u = import ./lib/flake-utils.nix { inherit nixpkgs; }; in …Keeps flake.nix readable without flake-parts.
Lab 1 — Audit current outputs
cd /path/to/your-flake
nix flake showJournal:
- Which outputs exist today?
- Which will exist by Day 32 (checks, shells, …)?
- Is
flake.nixalready painful?
Lab 2 — Pick and write the decision
Create docs/flake-style.md:
# Flake style
Decision: hand-rolled | flake-parts
Date: YYYY-MM-DD
Rationale: …
Rules:
- …Commit this even before code changes—forces intentionality.
Lab 3A — If flake-parts: adopt on a branch
git checkout -b experiment/flake-parts
# add input, rewrite outputs via mkFlake
nix flake lock
nix flake show
nixos-rebuild build --flake .#lab
sudo nixos-rebuild switch --flake .#lab # if build OKProve HM + host still activate.
Lab 3B — If hand-rolled: extract helpers
git checkout -b experiment/flake-discipline
# add systems + forAllSystems + formatter + devShells
nix flake show
nix fmt 2>/dev/null || true
nix develop -c true
nixos-rebuild build --flake .#labLab 4 — Merge the winner
Merge the experiment branch to your main lab branch. Delete half-finished alternatives. One style in flake.nix.
Lab 5 — Teach the style to “future you”
In docs/flake-style.md, document how to:
- Add a new
nixosConfigurationshost
- Add a per-system
devShell
- Update flake-parts (if used) or helpers (if not)
Common gotchas
| Symptom / mistake | What to do |
|---|---|
perSystem vs flake confusion |
Hosts go under flake; shells under perSystem |
Lost specialArgs / inputs |
Thread inputs explicitly after rewrite |
| HM module dropped in refactor | Re-check modules list post-migration |
| Double nixpkgs without follows | Keep follows on all companion inputs |
nix flake show empty-ish |
Eval error—run build to see full trace |
| Framework without docs | Write house docs the same day |
Checkpoint
- Explicit house style documented
nix flake showreflects multi-output intent
- Host rebuild still works
formatterand/ordevShellsexist in the chosen style
- No hybrid mess left on main branch
- How-to for adding a host written
Commit
git add .
git commit -m "day29: flake-parts or hand-rolled discipline"Write three personal gotchas before continuing.
Tomorrow
Day 30 — Flake checks: make nix flake check fail when format/eval assumptions break—CI-shaped discipline before packaging stage.
Day 30 — Flake checks
Stage III · ~4h
Goal: Add flake checks so nix flake check exercises eval/format/build assumptions locally—the same shape CI will use later.
Why this day exists
A flake that only switches on your laptop is a private ritual. Checks turn “I hope this still evaluates” into a command:
nix flake checkToday is not full nixosTest mastery (Stage VI). Today is lightweight gates: format, build a shell, evaluate a host, maybe build a package.
Theory 1 — What checks are
Flake schema: checks.<system>.<name> → derivations. nix flake check builds them (with some special cases).
| Check type | Purpose |
|---|---|
| Format / lint | Style consistency |
nixosSystem eval/build |
Config still evaluates |
mkShell build |
DevShell inputs resolve |
| Small package build | Packaging sanity |
nixosTest |
VM integration (later) |
Failed check → non-zero exit → CI red.
Theory 2 — Hand-rolled checks skeleton
# inside outputs, with forAllSystems / pkgsFor from Day 29
checks = forAllSystems (system:
let
pkgs = pkgsFor system;
in {
# 1) formatter dry-run style check
format = pkgs.runCommand "check-format" {
buildInputs = [ pkgs.nixpkgs-fmt ];
} ''
set -euo pipefail
# copy sources; fail if nixpkgs-fmt wants changes
mkdir -p $out
# For a real check, point at your nix files via path builtins carefully.
# Simpler starter: succeed if formatter exists
command -v nixpkgs-fmt > $out/fmt-path
'';
# 2) devShell builds
devshell = self.devShells.${system}.default;
# 3) host evaluation via build of system toplevel (heavy but strong)
# lab-host = self.nixosConfigurations.lab.config.system.build.toplevel;
});Building the full toplevel is an excellent check but can be slow and architecture-bound. For a laptop-only x86_64-linux lab, pinning checks to that system is fine.
Lighter host check — eval only patterns
Some people use pkgs.writeText of a JSON dump of options they care about, or nixos-rebuild build in CI scripts without putting toplevel in checks. Either works if documented.
Theory 3 — flake-parts checks
Under perSystem:
perSystem = { self', pkgs, ... }: {
checks = {
devshell = self'.devShells.default;
# treefmt / pre-commit style modules exist in the ecosystem—optional
};
};Community modules (treefmt-nix, git-hooks.nix / pre-commit-hooks) are optional accelerators—do not require them today if time is short.
Theory 4 — Format check that actually fails
A useful format check:
format = pkgs.runCommand "check-nix-fmt" {
nativeBuildInputs = [ pkgs.nixpkgs-fmt ];
src = ./.; # careful: large contexts; often better to pass explicit file list
} ''
set -euo pipefail
cp -r $src tree
chmod -R u+w tree
cd tree
# list your nix files
find . -name '*.nix' -print0 | xargs -0 nixpkgs-fmt --check
mkdir -p $out
touch $out/ok
'';Notes:
--checkmust exist for your formatter CLI (nixpkgs-fmt supports check mode;nix fmtworkflows vary)
- Copying entire flake including
.gitcan be heavy—trim withlib.cleanSource
src = lib.cleanSource ./.;Theory 5 — What belongs in check vs push-time CI only
Local nix flake check |
CI later (Stage VI) |
|---|---|
| Format | Same + matrix systems |
| Shell / small builds | Cache push |
| Optional host toplevel | Full VM tests |
Keep local checks fast enough that you run them. A 40-minute check nobody runs is theater.
Theory 6 — nix flake check UX
nix flake check
nix flake check -L # logs
nix build .#checks.x86_64-linux.format -LIndividual check attrs help debug without running the full set.
Worked example — Practical minimal set for Stage III
checks = forAllSystems (system:
let pkgs = pkgsFor system; in {
shell = self.devShells.${system}.default;
# only on the host architecture you actually use:
}) // {
# top-level non-per-system alternative patterns exist; prefer per-system consistency
};Plus on x86_64-linux only:
# conceptual
lab-toplevel = self.nixosConfigurations.lab.config.system.build.toplevel;If aarch64 eval of an x86-only host is nonsense, do not genAttrs that check onto all systems blindly—gate by system.
checks = forAllSystems (system:
let pkgs = pkgsFor system; in {
shell = self.devShells.${system}.default;
} // nixpkgs.lib.optionalAttrs (system == "x86_64-linux") {
lab-toplevel = self.nixosConfigurations.lab.config.system.build.toplevel;
});Lab 1 — Baseline without checks
cd /path/to/your-flake
nix flake show
nix flake check || trueNote current behavior (may pass with zero checks or warn).
Lab 2 — Add devShell as a check
Wire checks.<system>.shell = self.devShells.${system}.default (or flake-parts equivalent).
nix build .#checks.x86_64-linux.shell -LLab 3 — Add a format check that can fail
- Implement format check on
*.nix
- Run
nix flake check
- Break formatting in a temp edit
- Confirm check fails
- Restore format
# example break
# add ugly spacing in a nix file, then:
nix flake checkLab 4 — Optional host toplevel check
If disk/time allow:
nix build .#checks.x86_64-linux.lab-toplevel -LRecord wall-clock time. If > your patience budget, document “CI-only toplevel” and keep shell+format local.
Lab 5 — Developer loop script (optional)
# tools/check.sh
#!/usr/bin/env bash
set -euo pipefail
nix flake check -LNot required if you prefer raw commands.
Lab 6 — Journal: CI shape
Write the future CI job in five lines:
checkout
install nix
nix flake check
# later: deploy job separate
Common gotchas
| Symptom / mistake | What to do |
|---|---|
| Checks empty / not discovered | Must be under checks output correctly |
| IFD surprises slow checks | Avoid unnecessary import-from-derivation |
| Building host on wrong system | optionalAttrs gate |
| Format check always succeeds | You didn’t pass real sources |
| Check context includes secrets | Don’t; keep secrets out of flake source |
nix flake check downloads the world |
First run cold; then cached—note substituters |
Checkpoint
- At least two meaningful checks (e.g. shell + format)
nix flake checkfails when format intentionally broken
- Know how to build one check attribute
- Host toplevel check added or explicitly deferred with reason
- Future CI sketch written
- Checks are fast enough to run before commit
Commit
git add .
git commit -m "day30: flake checks for format and shell"Write three personal gotchas before continuing.
Tomorrow
Day 31 — Overlays light: pin or patch one package for host and shell without forking all of nixpkgs.
Day 31 — Overlays light
Stage III · ~4h
Goal: Apply a small overlay so the same package change is visible to the NixOS host and to devShells—without forking nixpkgs or cargo-culting overlay stacks.
Overlays are sharp tools. Prefer options, package overrides in a single module, or upstream pins when they suffice. Today teaches the mechanism so you are not helpless—not so every problem becomes an overlay.
Why this day exists
Sooner or later you need:
- A newer/older minor of one tool than nixpkgs 26.05 ships
- A one-line patch to a package you use
- A renamed or wrapped package for your lab
Overlays let you modify the pkgs fixed-point. Done lightly, they are elegant. Done heavily, they make upgrades impossible.
Theory 1 — What an overlay is
An overlay is a function:
final: prev: {
# new or overridden attrs
}| Argument | Meaning |
|---|---|
prev |
Package set before this overlay |
final |
Package set after all overlays (fixed-point)—use for deps on other overridden pkgs |
final: prev: {
hello = prev.hello.overrideAttrs (old: {
# example: add a patch or change configure flags
});
}Theory 2 — Wiring overlays on NixOS
# modules/common/nixpkgs.nix
{ ... }:
{
nixpkgs.overlays = [
(import ../../overlays/hello.nix)
# or inline:
# (final: prev: { ... })
];
}With home-manager.useGlobalPkgs = true, HM sees the same overlayed pkgs. That is why Day 25 set that flag.
Flake-level pkgs for shells/checks
pkgsFor = system: import nixpkgs {
inherit system;
overlays = [
(import ./overlays/hello.nix)
];
};Consistency rule: the overlay list used for nixosConfigurations should match the list used for devShells / checks when you care about the same tools—or document intentional differences.
Theory 3 — overrideAttrs vs override vs new package
| Tool | When |
|---|---|
pkg.override { ... } |
Change callPackage parameters |
pkg.overrideAttrs |
Change derivation attributes (patches, version, src) |
New attr final.mytool = ... |
Add a package name without clobbering |
Example patch (illustrative):
# overlays/hello.nix
final: prev: {
hello = prev.hello.overrideAttrs (old: {
patches = (old.patches or []) ++ [
# ./patches/hello-example.patch
];
});
}If you do not have a real patch today, use a harmless visible change such as pname suffix only for learning—or prefer adding a wrapper package instead of mutating hello:
final: prev: {
hello-lab = prev.writeShellScriptBin "hello-lab" ''
echo "lab overlay wrapper"
exec ${prev.hello}/bin/hello "$@"
'';
}Wrappers are often safer than overriding core packages.
Theory 4 — Composition order
nixpkgs.overlays = [ overlayA overlayB ];Later overlays see earlier results via prev chain. Conflicts: last writer wins for the same attr. Keep one file per concern and a short list.
Theory 5 — When not to overlay
| Need | Prefer |
|---|---|
| Enable a service option | NixOS module option |
| Package only for one project | Project devShell / packages |
| Temporary try | nix shell / branch pin |
| Huge fork of desktop stack | Separate nixpkgs input (advanced) |
Overlays that rewrite half of python3Packages are Stage V+ territory—and often a mistake.
Theory 6 — Pinning a package from another nixpkgs (light)
Sometimes you want one package from a newer pin without moving the whole OS:
# advanced sketch — use sparingly
# inputs.nixpkgs-unstable.url = "...";
# final: prev: {
# fancy = inputs.nixpkgs-unstable.legacyPackages.${prev.system}.fancy;
# }Costs: two package graphs, closure size, ABI surprises. For today, a wrapper or small overrideAttrs is enough.
Lab 1 — Create overlay file
mkdir -p overlays
# add hello-lab.nix and default.nix as aboveLab 2 — Attach to NixOS
Import overlay list via nixpkgs.overlays. Add pkgs.hello-lab to system or HM packages. Rebuild:
sudo nixos-rebuild switch --flake .#lab
hello-labLab 3 — Attach to devShell / checks pkgs
Ensure pkgsFor (or flake-parts pkgs) uses the same overlays.
nix develop -c hello-labIf only system has it, fix the flake pkgs construction.
Lab 4 — Prove HM sees it (if useGlobalPkgs)
# homes/alice snippet
home.packages = [ pkgs.hello-lab ];Rebuild; as user command -v hello-lab.
Lab 5 — Intentional override discipline
Journal:
- What you overlaid/wrapped and why
- How you will remove it when upstream catches up
- Whether a project-only shell would have been enough
Lab 6 — Negative space
Find one change you could do with an overlay but will do with a module option instead. Write it down—builds judgment.
Common gotchas
| Symptom / mistake | What to do |
|---|---|
pkgs.hello-lab missing |
Overlay not wired into that pkgs import |
| HM doesn’t see overlay | useGlobalPkgs false or separate pkgs |
| Infinite recursion in overlay | Using final incorrectly; simplify |
| Overlay only on system | Wire flake pkgsFor too |
| Overlaying all of nixpkgs for fun | Revert; minimize surface |
| Patch path wrong | Path relative to overlay file / flake root |
Checkpoint
- Overlay or wrapper package defined in repo
- Visible on NixOS host packages
- Visible in devShell or documented intentional difference
- HM consistency verified if applicable
- Removal plan written
- Judgment note: when not to overlay
Commit
git add .
git commit -m "day31: light overlays shared by host and shell"Write three personal gotchas before continuing.
Tomorrow
Day 32 — Stage III gate: prove host + Home Manager + devShell + checks live in one clean flake—exit criteria for daily-driver shape.
Day 32 — Stage III gate
Stage III · ~4h
Goal: Prove the Stage III exit criteria: a workstation/server skeleton where host + Home Manager + project/devShell workflow + flake checks live in one coherent flake layout—and a new machine of the same role is “clone + hardware + rebuild.”
Gates are not content cram days. They are proof. If something is missing, fix it here before Stage IV secrets and services multiply the mess.
Why this day exists
Stage III introduced structure without which Stage IV becomes dangerous:
| Stage III skill | Stage IV needs it because… |
|---|---|
| hosts/modules/homes | Secrets modules attach cleanly |
| Lock discipline | New inputs (sops-nix) must pin sanely |
| HM as NixOS module | User + system activate together |
| direnv/devShell | Service dev doesn’t bloat system |
| checks | Catch eval breakage before deploy |
| light overlays | Occasional pin without chaos |
Today you demonstrate, not skim blog posts.
Theory 1 — Official exit criteria (from syllabus)
New machine of same role = clone flake + hardware config + rebuild.
Operationalization:
- Flake is the source of truth (no critical imperative snowflakes)
- Layout is navigable by someone else (or future you)
- User environment is HM-managed for shell/git basics
- Project tools are not only global profiles
nix flake check(your subset) passes
- Rebuild from cold boot still works; rollback still works
Theory 2 — Evidence pack (what “done” looks like)
Prepare a short evidence directory (journal or docs/stage-iii-evidence.md):
# Stage III evidence
## Tree
(paste tree -L 3)
## Commands
- nix flake show
- nix flake check
- nixos-rebuild switch --flake .#lab
- direnv / nix develop proof
- home-manager user programs proof
## Pins
- nixpkgs rev: …
- home-manager rev: …
## Known debt
- …Theory 3 — Role skeleton checklist
Host (hosts/lab)
- Hostname set
- Hardware imported
- Common modules imported
- SSH keys-only access still true
- Firewall stance known (even if simple)
system.stateVersionset
Modules
- Common policy split (users/ssh/nix/…)
- At least one custom module still present from Stage II
- Overlay wiring intentional
Homes
- HM NixOS module wired
- User programs: shell + git
- At least one managed file or XDG config
- Optional user service still present
Flake outputs
nixosConfigurations.<host>
devShellsor project-flake workflow documented
checksnonempty and passing
formatteroptional but nice
- Style doc (flake-parts vs hand-rolled)
Theory 4 — Clone drill (mental or real)
Ideal proof on a second VM:
git clone <your-flake-url> lab && cd lab
# generate or copy hardware-configuration for THIS machine
sudo nixos-rebuild switch --flake .#labIf you lack a second VM today: walk the steps dry and list what would differ (disk UUIDs, hostname, network).
Theory 5 — Debt register
Unfinished work is allowed only if listed:
| Debt | Stage to pay |
|---|---|
| Secrets still plaintext | IV (33–35) urgently |
| No format check | Fix today if <1h |
| Theming rabbit hole | Never required |
| Multi-host | Later stages |
Do not enter Stage IV with “I will structure the flake later.”
Lab 1 — Tree and show
cd /path/to/your-flake
find . -type f \( -name '*.nix' -o -name '*.md' -o -name '.envrc' \) | sort
nix flake metadata
nix flake showPaste into evidence doc.
Lab 2 — Full rebuild proof
sudo nixos-rebuild switch --flake .#labVerify:
hostname
systemctl is-active sshd
git config --global --get user.name || true
command -v direnvLab 3 — Checks pass
nix flake check -LFix failures before continuing. If toplevel check is too heavy, ensure documented lighter checks still pass.
Lab 4 — DevShell / direnv proof
cd ~/lab/90daysofx/02-nixos/day28/demo-app # or your project
# show tools available via direnv or nix develop
nix develop -c trueLab 5 — Rollback still works (confidence)
# list generations
sudo nix-env -p /nix/var/nix/profiles/system --list-generations | tailYou do not need to break the system today if already proven in Stage II—but confirm you still know the boot menu / switch --rollback path. Write the commands in evidence.
Lab 6 — Peer-style review of your own flake
Answer in writing:
- Where do I add a second host?
- Where do I add a system service module?
- Where do I add a user package?
- How do I update only nixpkgs?
- What is still imperative on this machine?
If any answer is “I don’t know,” fix docs or layout now.
Lab 7 — Gate commit and tag (optional)
git add .
git commit -m "day32: stage III gate — host+HM+shell+checks"
git tag -a stage-iii-gate -m "Stage III exit"Tags help Stage VIII capstone archaeology.
Theory 6 — Stage III → IV interface contract
Stage IV will attach:
| Concern | Expected hook in your flake |
|---|---|
| sops-nix / agenix | module import + secrets files outside store plaintext |
| reverse proxy | modules/services/ style |
| data service | state dirs + secrets paths |
| firewall matrix | docs/ports.md living doc |
If your layout has nowhere obvious to put those, redesign today.
Theory 7 — Pin coherence check
nix flake metadata
# home-manager should follow nixpkgs when you set followsMismatched HM release vs nixpkgs (e.g. HM 25.11 on nixpkgs 26.05) is a classic eval footgun. Align release-26.05 tracks.
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
home-manager.url = "github:nix-community/home-manager/release-26.05";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
};Worked example — Minimal checks still count
checks.${system}.hostname = pkgs.runCommand "check-hostname" { } ''
# example pattern only — prefer real eval checks you already have
echo ok > $out
'';Better: evaluate that nixosConfigurations.lab exists and maybe that config.networking.hostName matches expectation (as you did in Day 30). Gate requires passing checks, not toy checks that ignore the host.
Lab 8 — HM vs system package boundary audit
List five tools and where they live:
| Tool | systemPackages | HM home.packages | devShell |
|---|---|---|---|
| git | ? | ? | ? |
Prefer: system or HM for daily drivers; devShell for language toolchains.
Lab 9 — flake.lock age honesty
git log -1 --format=%ci -- flake.lockIf the lock is months stale on a public-facing host, schedule an update session—not a blind full update on this gate day unless you have time to smoke-test.
Common gotchas
| Symptom / mistake | What to do |
|---|---|
| Gate day used to learn HM from zero | Return to Day 25; don’t fake the gate |
| Checks failing “but switch works” | Fix checks—CI will not care about switch on your laptop |
| Evidence only in head | Write the evidence file |
| Second host impossible today | Dry-run clone steps still required |
| Secret debt ignored | Escalate to Day 33 first thing next stage |
| Layout relapsed into monolith | Re-split before Stage IV |
Checkpoint — Stage III exit
- Layout: hosts / modules / homes
- Lock policy exists; lock committed
- HM as NixOS module; shell + git managed
- Files/XDG or programs configs real
- direnv/devShell workflow works
- Flake style chosen and documented
nix flake checkpasses
- Light overlay optional but understood
- Evidence doc written
- Clone + hardware + rebuild story clear
Commit
git add .
git commit -m "day32: stage III gate"Write three personal gotchas before continuing.
Tomorrow
Day 33 — Secret problem: why strings in Nix are dangerous, how the store leaks “secrets,” and what patterns Stage IV will replace.