From 66d368555d6796f32cd63884c2dbbd636a5e2383 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Wed, 15 Apr 2026 15:47:10 +0200 Subject: [PATCH] WIP --- notes/001-glossary.md | 58 ++++++++++++++++++++--------------------- notes/002-nix-primer.md | 56 +++++++++++++++++++-------------------- notes/003-flakes.md | 36 ++++++++++++------------- 3 files changed, 75 insertions(+), 75 deletions(-) diff --git a/notes/001-glossary.md b/notes/001-glossary.md index 35ba31a..3cfc0d9 100644 --- a/notes/001-glossary.md +++ b/notes/001-glossary.md @@ -4,47 +4,47 @@ Core vocabulary you'll hit in the first hour of Nix. Skim, don't memorize; come ## The Store and Builds -- **Nix store**: `/nix/store`. Immutable, content-addressed directory where *everything* Nix builds lives. Each entry is prefixed by a hash of its inputs. -- **Store path**: a single entry under `/nix/store`, e.g. `/nix/store/abc123…-hello-2.12.1`. The hash makes it unique per set of build inputs. -- **Derivation (`.drv`)**: a *recipe* for a build, covering inputs, builder script, env vars, and outputs. Produced by evaluating Nix code. Not the built artifact itself. -- **Realisation / "realising a derivation"**: actually executing the `.drv` to produce the output store path(s). "Build" colloquially means this. -- **Output path**: the store path a derivation produces. A derivation can have multiple outputs (e.g. `out`, `dev`, `man`, `lib`). -- **Closure**: a store path plus all its runtime dependencies, transitively. What you need to copy to another machine to actually use something. -- **Hash**: content/input hash that names store paths. Changing any input changes the hash, which changes the path; this is how Nix guarantees no cache collisions. -- **Fixed-output derivation (FOD)**: a derivation whose output hash you declare up front (e.g. source tarballs, `fetchurl`). Allowed network access during build; everything else is sandboxed offline. +- Nix store: `/nix/store`. Immutable, content-addressed directory where *everything* Nix builds lives. Each entry is prefixed by a hash of its inputs. +- Store path: a single entry under `/nix/store`, e.g. `/nix/store/abc123…-hello-2.12.1`. The hash makes it unique per set of build inputs. +- Derivation (`.drv`): a *recipe* for a build, covering inputs, builder script, env vars, and outputs. Produced by evaluating Nix code. Not the built artifact itself. +- Realisation, or "realising a derivation": actually executing the `.drv` to produce the output store path(s). "Build" colloquially means this. +- Output path: the store path a derivation produces. A derivation can have multiple outputs (e.g. `out`, `dev`, `man`, `lib`). +- Closure: a store path plus all its runtime dependencies, transitively. What you need to copy to another machine to actually use something. +- Hash: content/input hash that names store paths. Changing any input changes the hash, which changes the path; this is how Nix guarantees no cache collisions. +- Fixed-output derivation (FOD): a derivation whose output hash you declare up front (e.g. source tarballs, `fetchurl`). Allowed network access during build; everything else is sandboxed offline. ## Language -- **Nix (the language)**: lazy, purely functional, dynamically typed. Every `.nix` file is one expression. -- **Attrset**: `{ a = 1; b = "x"; }`. The main data structure. -- **Lambda**: `x: x + 1` or `{ a, b }: a + b`. Functions take one argument; multi-arg functions are attrset-destructured. -- **`let ... in`**: local bindings, for example `let x = 1; in x + 1`. -- **`with expr; body`**: brings `expr`'s attrs into scope for `body`. Common in `with pkgs; [ hello jq ]`. Useful but can shadow bindings, so use sparingly. -- **`inherit`**: shorthand to pull names from outer scope into an attrset: `{ inherit pkgs system; }` ≡ `{ pkgs = pkgs; system = system; }`. -- **`import`**: evaluate another `.nix` file. `import ./foo.nix { }` imports and calls it. -- **`callPackage`**: a nixpkgs convention that auto-supplies function arguments from a package set. You'll see it everywhere in nixpkgs. +- Nix (the language): lazy, purely functional, dynamically typed. Every `.nix` file is one expression. +- Attrset: `{ a = 1; b = "x"; }`. The main data structure. +- Lambda: `x: x + 1` or `{ a, b }: a + b`. Functions take one argument; multi-arg functions are attrset-destructured. +- `let ... in`: local bindings, for example `let x = 1; in x + 1`. +- `with expr; body`: brings `expr`'s attrs into scope for `body`. Common in `with pkgs; [ hello jq ]`. Useful but can shadow bindings, so use sparingly. +- `inherit`: shorthand to pull names from outer scope into an attrset: `{ inherit pkgs system; }` ≡ `{ pkgs = pkgs; system = system; }`. +- `import`: evaluate another `.nix` file. `import ./foo.nix { }` imports and calls it. +- `callPackage`: a nixpkgs convention that auto-supplies function arguments from a package set. You'll see it everywhere in nixpkgs. ## Package Management -- **nixpkgs**: the community-maintained collection of Nix expressions for ~100k packages. Lives at `github:NixOS/nixpkgs`. -- **Channel**: a named, periodically-updated snapshot of nixpkgs (e.g. `nixos-unstable`, `nixos-25.11`). Pre-flakes way to pin. Flakes mostly replace this with lockfiles. -- **Overlay**: a function that extends or overrides a package set. Lets you patch, pin, or add packages without forking nixpkgs. -- **Profile**: a symlink tree representing "what's installed" for a user or system. `nix-env`, `nix profile`, NixOS, and home-manager all manage profiles. -- **Generation**: a versioned snapshot of a profile. Every change creates a new generation; old ones stay until garbage-collected. This is how rollback works. -- **Garbage collection (`nix-collect-garbage`)**: deletes store paths not reachable from any "GC root" (profiles, running processes, and `result` symlinks). +- nixpkgs: the community-maintained collection of Nix expressions for ~100k packages. Lives at `github:NixOS/nixpkgs`. +- Channel: a named, periodically-updated snapshot of nixpkgs (e.g. `nixos-unstable`, `nixos-25.11`). Pre-flakes way to pin. Flakes mostly replace this with lockfiles. +- Overlay: a function that extends or overrides a package set. Lets you patch, pin, or add packages without forking nixpkgs. +- Profile: a symlink tree representing "what's installed" for a user or system. `nix-env`, `nix profile`, NixOS, and home-manager all manage profiles. +- Generation: a versioned snapshot of a profile. Every change creates a new generation; old ones stay until garbage-collected. This is how rollback works. +- Garbage collection (`nix-collect-garbage`): deletes store paths not reachable from any "GC root" (profiles, running processes, and `result` symlinks). ## Flakes -- **Flake**: a directory with a `flake.nix`. Has pinned `inputs` and structured `outputs`. Reproducible, composable, and schema-driven. -- **`flake.lock`**: JSON file pinning the exact revision (and hash) of each input. Commit it. -- **Flake reference**: URL-like string identifying a flake: `github:NixOS/nixpkgs`, `path:./foo`, `git+https://…`, or `nixpkgs` (registry alias). -- **Registry**: short-name to flake-ref mapping. `nixpkgs` resolves via the global registry by default. -- **Pure evaluation**: flakes evaluate in a sandbox with no env vars, no arbitrary filesystem reads, and only declared inputs. This is what makes them reproducible. +- Flake: a directory with a `flake.nix`. Has pinned `inputs` and structured `outputs`. Reproducible, composable, and schema-driven. +- `flake.lock`: JSON file pinning the exact revision (and hash) of each input. Commit it. +- Flake reference: URL-like string identifying a flake: `github:NixOS/nixpkgs`, `path:./foo`, `git+https://…`, or `nixpkgs` (registry alias). +- Registry: short-name to flake-ref mapping. `nixpkgs` resolves via the global registry by default. +- Pure evaluation: flakes evaluate in a sandbox with no env vars, no arbitrary filesystem reads, and only declared inputs. This is what makes them reproducible. ## NixOS and home-manager -- **NixOS module**: a function `{ config, lib, pkgs, ... }: { options = …; config = …; }`. The unit of NixOS configuration. -- **home-manager**: user-level declarative config for dotfiles, per-user packages, and services. Works standalone or as a NixOS module. +- NixOS module: a function `{ config, lib, pkgs, ... }: { options = …; config = …; }`. The unit of NixOS configuration. +- home-manager: user-level declarative config for dotfiles, per-user packages, and services. Works standalone or as a NixOS module. ## CLI: Old vs. New diff --git a/notes/002-nix-primer.md b/notes/002-nix-primer.md index 6f4d78e..c3cae76 100644 --- a/notes/002-nix-primer.md +++ b/notes/002-nix-primer.md @@ -1,9 +1,9 @@ # Nix Primer -Nix is **two things bundled**: +Nix is two things bundled: -1. **A pure, lazy functional language** for describing builds. -2. **A package manager** that takes those descriptions and produces content-addressed store paths. +1. A pure, lazy functional language for describing builds. +2. A package manager that takes those descriptions and produces content-addressed store paths. Confusingly, both are called "Nix." Separate them in your head and everything gets easier. @@ -20,8 +20,8 @@ Every `nix build` or `nix develop` runs in two phases: (pure, lazy) (on disk) (content-addressed) ``` -- **Evaluation** runs the Nix language. Pure, in-memory, fast. Produces `.drv` files. -- **Realisation** actually builds things. Sandboxed. Slow. Outputs go to `/nix/store`. +- Evaluation runs the Nix language. Pure, in-memory, fast. Produces `.drv` files. +- Realisation actually builds things. Sandboxed. Slow. Outputs go to `/nix/store`. You can evaluate without building (`nix eval`, `nix-instantiate`) and you can build without touching the language if you already have a `.drv`. Most errors you'll see are *eval* errors: typos in attrsets, missing arguments, and undefined names. @@ -107,7 +107,7 @@ import ./foo.nix { x = 1; } # if foo.nix is a function, also call it ## 3. Derivations: The Core Primitive -A **derivation** is "a build, described as data." You create one with `derivation { … }` (low-level) or `pkgs.stdenv.mkDerivation { … }` (the nixpkgs wrapper you'll actually use). +A derivation is "a build, described as data." You create one with `derivation { … }` (low-level) or `pkgs.stdenv.mkDerivation { … }` (the nixpkgs wrapper you'll actually use). Minimal example: @@ -129,10 +129,10 @@ pkgs.stdenv.mkDerivation { Key ideas: -- **`$out`** is the output store path the build writes into. -- **Phases** (`unpackPhase`, `buildPhase`, `installPhase`, …) are shell snippets `stdenv` runs in order. Override the ones you need. -- **No network** in the build sandbox, except for fixed-output derivations (whose hash you declare up front). -- **Every input** (source, compiler, env vars) becomes part of the hash. Change anything and you get a fresh store path. +- `$out` is the output store path the build writes into. +- Phases (`unpackPhase`, `buildPhase`, `installPhase`, …) are shell snippets `stdenv` runs in order. Override the ones you need. +- No network in the build sandbox, except for fixed-output derivations (whose hash you declare up front). +- Every input (source, compiler, env vars) becomes part of the hash. Change anything and you get a fresh store path. --- @@ -140,13 +140,13 @@ Key ideas: Traditional package managers put files in `/usr/bin`, `/usr/lib`, and similar shared locations. One version at a time. Upgrades mutate shared state. -Nix puts every build in `/nix/store/-/` with the hash derived from **all its inputs** (recursively). That gives you: +Nix puts every build in `/nix/store/-/` with the hash derived from all its inputs (recursively). That gives you: -- **Atomic upgrades and rollbacks**: switching "versions" is just switching symlinks. -- **Multiple versions coexisting**: they live at different hashes. -- **Reproducibility**: same inputs produce the same hash and the same output (in theory; in practice, 99% there). -- **Binary caching**: if someone else already built exactly these inputs, you can download their output. This is what `cache.nixos.org` does. -- **Garbage-collectable**: anything not referenced by a "GC root" can be deleted safely. +- Atomic upgrades and rollbacks: switching "versions" is just switching symlinks. +- Multiple versions coexisting: they live at different hashes. +- Reproducibility: same inputs produce the same hash and the same output (in theory; in practice, 99% there). +- Binary caching: if someone else already built exactly these inputs, you can download their output. This is what `cache.nixos.org` does. +- Garbage-collectable: anything not referenced by a "GC root" can be deleted safely. --- @@ -154,12 +154,12 @@ Nix puts every build in `/nix/store/-/` with the hash derived from * Ordered roughly by power-to-complexity: -1. **`nix run nixpkgs#cowsay -- moo`**: run a package without installing. -2. **`nix shell nixpkgs#jq nixpkgs#ripgrep`**: throwaway shell with those tools. -3. **Dev shell via `flake.nix`**: per-project pinned toolchain. *This is the most common thing flakes are used for.* -4. **`nix build`**: produce artifacts (binaries, containers, and ISOs). -5. **NixOS**: declare your whole OS config (packages, services, users, and network) in Nix. -6. **home-manager**: same idea, at the user level. +1. `nix run nixpkgs#cowsay -- moo`: run a package without installing. +2. `nix shell nixpkgs#jq nixpkgs#ripgrep`: throwaway shell with those tools. +3. Dev shell via `flake.nix`: per-project pinned toolchain. *This is the most common thing flakes are used for.* +4. `nix build`: produce artifacts (binaries, containers, and ISOs). +5. NixOS: declare your whole OS config (packages, services, users, and network) in Nix. +6. home-manager: same idea, at the user level. Each step up locks in more of your environment. You don't need to adopt all of it at once. @@ -189,12 +189,12 @@ nix why-depends ./result nixpkgs#glibc # trace dependency chains ## 7. Gotchas That Trip Up Newcomers -- **Lists use spaces, not commas.** `[ 1 2 3 ]`. Commas are a syntax error. -- **`rec { }`** makes an attrset self-referential. Without `rec`, attrs can't reference each other. You'll see this in package definitions. -- **Paths vs. strings.** `./foo` is a path (copied to store on use). `"./foo"` is a string (not auto-copied). Mixing them causes surprises. -- **`builtins.*`** is the always-available low-level namespace. `lib.*` (from nixpkgs) is the high-level helper library. Both exist; they're different. -- **Evaluation errors are lazy.** An error deep in an unused branch only fires when you actually use it. `nix-instantiate --eval --strict` forces full evaluation. -- **Hash errors on first build of an FOD**: you'll see `got: sha256-…`. Copy that hash into your expression. This is expected workflow. +- Lists use spaces, not commas. `[ 1 2 3 ]`. Commas are a syntax error. +- `rec { }` makes an attrset self-referential. Without `rec`, attrs can't reference each other. You'll see this in package definitions. +- Paths vs. strings. `./foo` is a path (copied to store on use). `"./foo"` is a string (not auto-copied). Mixing them causes surprises. +- `builtins.*` is the always-available low-level namespace. `lib.*` (from nixpkgs) is the high-level helper library. Both exist; they're different. +- Evaluation errors are lazy. An error deep in an unused branch only fires when you actually use it. `nix-instantiate --eval --strict` forces full evaluation. +- Hash errors on first build of an FOD: you'll see `got: sha256-…`. Copy that hash into your expression. This is expected workflow. --- diff --git a/notes/003-flakes.md b/notes/003-flakes.md index c4c5c10..93c4c88 100644 --- a/notes/003-flakes.md +++ b/notes/003-flakes.md @@ -1,11 +1,11 @@ # Flakes -A **flake** is a directory containing `flake.nix` (and, once evaluated, `flake.lock`). It's a packaging convention on top of regular Nix that gives you: +A flake is a directory containing `flake.nix` (and, once evaluated, `flake.lock`). It's a packaging convention on top of regular Nix that gives you: -- **Pinned inputs** via `flake.lock` for reproducible builds. -- **A fixed output schema** so tools know where to look (`devShells..default`, `packages..default`, and so on). -- **Composability**, since flakes depend on other flakes by URL. -- **Pure evaluation**, with no silent dependence on `NIX_PATH`, env vars, or random files. +- Pinned inputs via `flake.lock` for reproducible builds. +- A fixed output schema so tools know where to look (`devShells..default`, `packages..default`, and so on). +- Composability, since flakes depend on other flakes by URL. +- Pure evaluation, with no silent dependence on `NIX_PATH`, env vars, or random files. Flakes are still marked "experimental" but are now the way most new Nix code is written. @@ -35,7 +35,7 @@ Two top-level keys matter: `inputs` and `outputs`. `description` is cosmetic. ### Inputs -Each input has a **flake reference** URL. Common forms: +Each input has a flake reference URL. Common forms: ``` github:owner/repo # latest default branch @@ -81,10 +81,10 @@ Default attrs `default` are picked when you omit the name: `nix build` ≡ `nix `flake.lock` pins the exact commit and NAR hash of every input, transitively. Generated or updated automatically on first use or via `nix flake update`. -- **Commit it.** Without it, "my flake" means different things on different machines. -- **Update explicitly.** `nix flake update` (all inputs) or `nix flake update nixpkgs` (one input). -- **Inspect.** `nix flake metadata` prints the resolved URLs and revisions. -- **`follows`** deduplicates: if both `flake-utils` and `foo` want their own `nixpkgs`, `follows = "nixpkgs";` forces them to use yours. Without this, you can end up with multiple nixpkgs copies and subtle version skew. +- Commit it. Without it, "my flake" means different things on different machines. +- Update explicitly. `nix flake update` (all inputs) or `nix flake update nixpkgs` (one input). +- Inspect. `nix flake metadata` prints the resolved URLs and revisions. +- `follows` deduplicates: if both `flake-utils` and `foo` want their own `nixpkgs`, `follows = "nixpkgs";` forces them to use yours. Without this, you can end up with multiple nixpkgs copies and subtle version skew. --- @@ -149,9 +149,9 @@ Flakes evaluate in pure mode: - No reading `NIX_PATH` or ``. - No arbitrary `builtins.getEnv` or reading files outside the flake. - `builtins.currentTime` and `builtins.currentSystem` are blocked. -- Source is pulled from the flake's input tree, not the working directory, unless you're in `self`. Files not tracked by git are **invisible** to the flake by default. +- Source is pulled from the flake's input tree, not the working directory, unless you're in `self`. Files not tracked by git are invisible to the flake by default. -This last point surprises people: **`git add` a file before `nix build` can see it.** (`nix build --impure` escapes this, but then you're not reproducible.) +This last point surprises people: `git add` a file before `nix build` can see it. (`nix build --impure` escapes this, but then you're not reproducible.) In exchange: anyone with your `flake.nix` plus `flake.lock` gets bit-identical evaluation. @@ -223,11 +223,11 @@ overlays.default = final: prev: { ## 8. Debugging Tips -- **`nix flake show --all-systems`** to see outputs for every platform, not just yours. -- **`nix eval .#packages.x86_64-linux.default.drvPath`** to get the `.drv` without building. -- **`nix eval --json .#devShells.x86_64-linux.default.buildInputs | jq`** to inspect shell deps. -- **`--show-trace`** on any command for full eval backtraces. -- **`nix repl .`** loads your flake's outputs into a REPL: useful for poking at attrs. +- `nix flake show --all-systems` to see outputs for every platform, not just yours. +- `nix eval .#packages.x86_64-linux.default.drvPath` to get the `.drv` without building. +- `nix eval --json .#devShells.x86_64-linux.default.buildInputs | jq` to inspect shell deps. +- `--show-trace` on any command for full eval backtraces. +- `nix repl .` loads your flake's outputs into a REPL: useful for poking at attrs. - If an input seems stuck on an old rev, check `flake.lock`; `nix flake update ` fixes it. --- @@ -238,4 +238,4 @@ overlays.default = final: prev: { - A tiny personal script: plain `default.nix` plus `nix-build` is still fine. - Large monorepos with complex CI: consider `flake-parts` or hybrid setups (flake for the interface, regular Nix for guts). -Flakes are a **packaging interface**, not a requirement. The old non-flake world still works; flakes just compose better once you have more than one Nix project. +Flakes are a packaging interface, not a requirement. The old non-flake world still works; flakes just compose better once you have more than one Nix project.