Day 4 — Classic CLI & Stage I Gate
Day 4 — Classic CLI & Stage I Gate
Day 9 — Classic CLI literacy
Stage I · ~4h (literacy + dual drills)
Goal: Map classic Nix commands and channel habits to the modern CLI—once—so legacy docs are readable without making classic tools your daily driver.
This day is literacy, not a lifestyle change. After today, default back to modern nix + flakes.
Why this day exists
Half the internet still says nix-env -iA nixpkgs.hello and nix-shell -p. If you cannot translate, you thrash. If you only learn classic, you fight 2026 defaults. Translation table + one dual run is enough.
Theory 1 — Same store, two UIs
| Layer | Shared | Differs |
|---|---|---|
| Store | /nix/store, derivations, GC roots |
— |
| Eval | Nix language | Entry points, purity defaults |
| UX | — | Flag names, flake awareness, UX polish |
Classic tools predate flakes. They still work. They are not the pedagogy default of this volume.
Theory 2 — Command map (core)
| Classic | Modern analogue | Notes |
|---|---|---|
nix-build -A hello |
nix build nixpkgs#hello |
Attr selection differs |
nix-build on default.nix |
nix build -f . / flake packages |
|
nix-shell -p hello |
nix shell nixpkgs#hello |
Ad-hoc tools |
nix-shell (dev shell.nix) |
nix develop (flake) / nix-shell still |
Project shells |
nix-env -iA nixpkgs.hello |
nix profile install nixpkgs#hello |
Prefer not to |
nix-env -q |
nix profile list |
|
nix-instantiate |
nix eval / derivation show |
Different shapes |
nix-store -q --requisites |
nix path-info -r |
Both fine |
nix-channel --update |
nix flake update (project) |
Different model |
nix-collect-garbage |
nix store gc / nix-collect-garbage |
Still common |
Theory 3 — Channels as history
Channel model
nix-channel --add https://nixos.org/channels/nixos-26.05 nixpkgs
nix-channel --update
nix-env -iA nixpkgs.hello
| Property | Effect |
|---|---|
| Mutable “latest on channel” | Drift between machines/days |
NIX_PATH / <nixpkgs> |
Ambient dependency |
| User or root channels | Confusing multi-user stories |
Flake model (preferred)
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
# flake.lock pins exact rev
| Property | Effect |
|---|---|
| Lock file | Shared truth |
| Explicit inputs | No ambient channel required |
| Per-project pins | Different projects can differ safely |
This volume: channels = read-only literacy. Do not build Stage I projects on channels.
Theory 4 — shell.nix / default.nix legacy layouts
Classic project shell
shell.nix:
{ pkgs ? import <nixpkgs> { } }:
pkgs.mkShell {
packages = [ pkgs.hello pkgs.git ];
}nix-shell # enters shell.nixModern
flake.nix → devShells.default → nix develop
You may keep a thin shell.nix for non-flake users, but your path is flakes.
Theory 5 — nix-env and imperative profiles
nix-env -iA nixpkgs.hello
nix-env -e hello
nix-env --list-generationsProblems as a primary workflow:
- Not recorded in project git
- Easy to create unreproducible laptops
- Diverges from NixOS declarative model
nix profile is the modern imperative cousin—same caution.
Theory 6 — Purity and flakes
Classic ad-hoc eval often sees your cwd and NIX_PATH freely. Flake evaluation is more pure by default (limited access to paths outside the flake). That is why git-tracked files matter (Day 8).
When classic examples “just import ./.”, flake equivalents may need self or explicit file inclusion.
Theory 7 — Reading old error messages
| Old-doc symptom | Translation |
|---|---|
attribute 'foo' missing in -A |
Wrong attr path / channel content |
file 'nixpkgs' was not found |
NIX_PATH / channel not set |
nix-env permission errors |
multi-user profile ownership |
| Blog says enable flakes differently | Check current Nix 2.34 docs |
Worked examples bank
Example A — Build hello both ways
# modern
nix build nixpkgs#hello --print-out-paths
# classic-ish
nix-build -E 'with import <nixpkgs> { }; hello' --no-out-link(If <nixpkgs> missing, skip classic path or install channel only for the drill.)
Example B — Shell both ways
nix shell nixpkgs#hello -c hello
nix-shell -p hello --run helloExample C — Query store both ways
P=$(nix build nixpkgs#hello --print-out-paths --no-link)
nix-store -q --references "$P"
nix path-info -r "$P" | headLab 1 — Build the translation table
mkdir -p ~/lab/90daysofx/02-nixos/day09
cd ~/lab/90daysofx/02-nixos/day09Create CLASSIC-MODERN.md with at least 10 rows you personally verified (or attempted):
| Task | Classic command | Modern command | Result notes |
|---|---|---|---|
| run hello | |||
| … |
Lab 2 — Dual path for day08 shell
Take your day08 project:
Modern (already works):
cd ~/lab/90daysofx/02-nixos/day08/myproject
nix develop -c git --versionClassic parallel: write shell.nix that mirrors the tool list using <nixpkgs> or a pinned fetch if you know how—only for the drill:
{ pkgs ? import <nixpkgs> { } }:
pkgs.mkShell {
packages = [ pkgs.git pkgs.hello pkgs.jq ];
}nix-shell --run 'git --version'Journal: which is easier to share with a teammate on another machine?
Lab 3 — Channel inspection (optional, disposable)
Only if you are willing to touch channels on a lab account:
nix-channel --list
# do not --update production machines casuallyIf empty: good—note “no channels; flakes only.”
Lab 4 — Doc archaeology
Find one external tutorial that uses classic CLI (search your history or web). Rewrite its first three commands into modern equivalents in your notes without running random untrusted scripts as root.
Lab 5 — Commitment statement
Write three sentences:
- When I will still use classic tools
- When I will refuse to (prefer modern)
- How I will handle future blog posts that only show
nix-env
Common gotchas
| Symptom | Theory |
|---|---|
| Following classic blog on NixOS host | May fight flakes/module setup |
| Installing channels “to fix” flakes | Usually wrong direction |
nix-shell vs nix develop confusion |
Different project conventions |
| Profile pollution from drills | Remove packages / use disposable user |
| Assuming channels == 26.05 pin | Channel lag vs flake lock rev |
Checkpoint
- Personal 10-row translation table
- Same task run modern + classic once
- Can explain channels vs flake locks
- Can explain why
nix-envis not the goal
- Default workflow remains modern
Commit
cd ~/lab/90daysofx/02-nixos/day09
git init 2>/dev/null || true
git add .
git commit -m "day09: classic to modern cli literacy table"Tomorrow
Day 10 — Stage I gate. Prove foundations: pinned flake shell, store/lock explanations, no channel dependency for the project.
Day 10 — Stage I gate
Stage I · ~4h (review + proof)
Goal: Close Stage I by demonstrating (not just claiming) that you understand store/closures, can pin a flake, enter a devShell on a clean context, and do not depend on channels for the project.
Why this day exists
Gates prevent “I watched myself type commands” from becoming “I own the material.” Stage II installs an OS; weak foundations make every rebuild mystical. Today is deliberate proof.
Theory 1 — What Stage I claimed
| Day | You should still own |
|---|---|
| 1 | Three faces of Nix; eval vs realize; flakes enabled |
| 2 | Store path anatomy; closure; GC roots; result trap |
| 3 | Attrsets, let, inherit, shallow merge |
| 4 | Function patterns, defaults, @, ellipsis |
| 5 | Laziness vs import; multi-file; lib taste |
| 6 | Derivation idea; trivial build; logs |
| 7 | Modern CLI intents |
| 8 | Project flake + lock + devShell |
| 9 | Classic ↔︎ modern map; channels as history |
Theory 2 — Exit criteria (syllabus contract)
You may leave Stage I when:
- You can explain a store path and a lock file plainly
nix developworks for your project after store is populated, without channel ceremony
- The project does not require
nix-channel/<nixpkgs>for normal use
- You can rebuild understanding from notes under mild pressure (gate checklist)
Theory 3 — Clean-context testing
“Works on my polluted laptop” is not a gate.
| Clean context options | Notes |
|---|---|
| New Unix user on same machine | Fast |
| Fresh VM with Nix installed | Better isolation |
| Container with Nix (advanced) | Optional |
| Friend’s machine | Social reproducibility |
Goal: clone/copy project → nix develop → tools appear.
Theory 4 — Offline after populate (honest version)
| Claim | Reality |
|---|---|
| Fully offline forever | Only if all inputs/substitutes already in store |
Offline nix develop after one online populate |
Often true for pinned shells |
| Eval may still want network | Unlocked floating inputs / registry |
Gate standard: document what you achieved offline, not aspirational purity.
Theory 5 — Knowledge you must verbalize
Prepare short answers (2–4 sentences each):
- Language vs package manager vs NixOS
- Why two versions of a library can coexist
- Definition of closure
- What a GC root is; name three kinds
- Eval failure vs build failure
- What
flake.lockpins
- Why
//is not module merge
- When to use
nix runvsnix develop
Worked examples bank
Example A — Gate project shape
stage1-gate/
flake.nix
flake.lock
README.md
notes/
answers.md
classic-modern.md # from day09 or link
Example B — Prove pin
nix flake metadata
# record: nixpkgs locked revExample C — Prove shell
nix develop -c bash -c 'command -v git; command -v hello || true; echo OK'Lab 1 — Assemble the gate repository
mkdir -p ~/lab/90daysofx/02-nixos/day10/stage1-gate/notes
cd ~/lab/90daysofx/02-nixos/day10/stage1-gateCopy or recreate a polished flake from day08:
inputs.nixpkgs.url→nixos-26.05
devShellswith at least:git, one language or ops tool,jqorhello
- README with exact enter instructions
nix flake lock
nix develop -c git --version
git init
git add .
git commit -m "day10: stage I gate flake"Lab 2 — Written exam (open notes first, then closed)
In notes/answers.md, answer Theory 5 prompts. Then collapse notes and re-answer on paper/blank buffer. Diff gaps; restudy weak days.
Lab 3 — Clean context run
Pick one:
Option A — second user
# as admin, create labuser if policy allows; or use a VMOption B — fresh directory + new shell env hygiene
# unset NIX_PATH to ensure you are not leaning on it
env -u NIX_PATH nix develop -c trueDocument exact steps that succeeded on the clean context.
Lab 4 — Store drill (observation)
# from within project after develop once
nix flake metadata --json | head -c 500
# build something tiny if you expose packages.default
nix build nixpkgs#hello --print-out-paths --no-link
P=$(nix build nixpkgs#hello --print-out-paths --no-link)
nix path-info -rS "$P" | tail -5Write:
- Full path of hello
- Rough requisites count
- Whether a
resultsymlink currently roots anything in the gate dir
Lab 5 — Gate checklist ceremony
Print and tick honestly:
Foundations checklist
- Three faces of Nix explained
- Store path anatomy explained
- Closure defined correctly
- GC roots: ≥3 kinds named
- Attrset +
let+inheritdemo exists in notes
- Function with defaults + ellipsis written once
- Multi-file import demo exists
- Derivation vs output path explained
- Modern CLI cheat-sheet exists
- Classic↔︎modern table exists
- Project flake with committed lock
nix developon clean context documented
- No required channel for project workflow
- Installer / Nix version recorded (
nix --version)
Any empty box → fix before Stage II.
Lab 6 — Capstone teach-back (10 minutes)
Record yourself or write a monologue:
“Here is how Nix keeps two library versions; here is what my lock file guarantees; here is how I enter my project shell on a new machine.”
If you cannot do it smoothly, restudy Days 1–2 and 8.
Theory 6 — What “flake owns the project” means in practice
Stage I does not require a NixOS host. It requires that your project is reproducible from:
git clone → cd project → nix develop
| Owned by flake | Not owned (yet) |
|---|---|
inputs + flake.lock |
Machine-wide channels |
devShells.default tools |
Random nix profile installs for this project |
| Documented enter path | Tribal “source this bashrc first” |
Minimal gate flake (26.05)
{
description = "Stage I gate — 90DaysOfX";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux"; # or aarch64-linux / darwin
pkgs = import nixpkgs { inherit system; };
in {
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [ git jq hello ];
};
};
}Commit both flake.nix and flake.lock.
Theory 7 — Registry vs project inputs (trap)
nix registry list may show a global nixpkgs → floating ref. That is fine for ad-hoc nix run nixpkgs#…, but the gate must not require it:
# Prefer flake-relative evaluation
nix develop .
# not: hope that <nixpkgs> matches your mental pinIf nix develop fails without ambient registry entries, your inputs are incomplete.
Theory 8 — Eval vs build failures (gate discrimination)
| Failure class | Example | Gate response |
|---|---|---|
| Eval | syntax, missing attr, infinite recursion | Fix expressions; nothing in store |
| Build | compile error, hash mismatch | Fix derivation / fetch |
| Runtime | binary missing from PATH in shell | Fix packages / nativeBuildInputs |
Verbalize which class you hit during the gate—vague “Nix is broken” is a fail.
Worked example D — Offline populate script (notes)
# online once
nix develop -c true
nix build nixpkgs#hello --no-link
# later offline (airplane mode): should still work if store+lock complete
# nix develop -c git --versionDocument exactly what still needs network on your machine (flake tarball fetches if not cached, substituters, etc.).
Lab 7 — Metadata and lock archaeology
cd ~/lab/90daysofx/02-nixos/day10/stage1-gate
nix flake metadata
nix flake metadata --json | jq -r '.locks.nodes.nixpkgs.locked.rev // .locks.nodes.nixpkgs.locked.narHash'Paste locked rev into notes/answers.md. That string is evidence you understand pins.
Lab 8 — Negative proof: channel independence
# If channels exist, show they are optional for the project
nix-channel --list 2>/dev/null || true
env -u NIX_PATH nix develop -c trueIf this fails, fix the flake until it passes. Stage II installs NixOS; do not carry channel dependency into it.
Common gotchas
| Symptom | Theory |
|---|---|
Gate flake uses <nixpkgs> |
Fails purity/channel independence |
| Lock not in git | Teammate gets different revs |
| Shell only works with ambient registry | Inputs not declared |
| “I understand” but blank answers | Theory not integrated |
| Aggressive GC mid-gate | Lost realizations; re-populate |
Checkpoint
- All Stage I checklist boxes ticked
- Gate repo committed with lock
- Clean-context
nix developdocumented
- Written answers for Theory 5
- Ready to risk a disposable VM for NixOS
Commit
cd ~/lab/90daysofx/02-nixos/day10/stage1-gate
git add .
git commit -m "day10: stage I gate passed"Tomorrow
Day 11 — Install NixOS lab. Disposable VM (preferred), UEFI, first boot of NixOS 26.05 “Yarara”—the machine Stage II will own with flakes.