NixOS configuration for the thinkpad, managed via flakes with Home Manager for dotfiles.
- Fresh NixOS install (USB boot, minimal ISO)
- Git available (
nix-shell -p gitif not yet installed)
Clone the required repos:
mkdir -p ~/git/cwage
cd ~/git/cwage
git clone git@github.com:cwage/nix-workstation.git
git clone git@github.com:cwage/dotfiles.git
git clone git@github.com:cwage/bin.gitBuild and activate:
cd ~/git/cwage/nix-workstation
sudo nixos-rebuild switch --flake .#thinkpadEdit files in hosts/thinkpad/ or flake.nix, then:
sudo nixos-rebuild switch --flake .#thinkpadThe dotfiles flake input points at github:cwage/dotfiles, so a plain nix flake update dotfiles only picks up changes that are already pushed. To test local, uncommitted edits in ~/git/cwage/dotfiles first, override the input with a path: reference for that one rebuild:
cd ~/git/cwage/nix-workstation
sudo nixos-rebuild switch --flake .#thinkpad --override-input dotfiles path:/home/cwage/git/cwage/dotfiles --no-write-lock-filepath:(notgit+file:) is used so the dirty working tree is included, not just committed files.--no-write-lock-filekeeps the override from being written intoflake.lock; without the flag,flake.lockstays pointed at the GitHub revision anyway, but this avoids a root-owned edit to it.sudo nixos-rebuild test ...with the same flags works too if you don't want the test build added to the bootloader.
Once you're happy, commit and push the dotfiles, then update the lock to the pushed revision and rebuild normally:
cd ~/git/cwage/nix-workstation
nix flake update dotfiles
sudo nixos-rebuild switch --flake .#thinkpad# Dry run - evaluate only, don't build or activate
nixos-rebuild dry-build --flake .#thinkpad
# Build without activating (test that it compiles)
nixos-rebuild build --flake .#thinkpad
# Build and activate, but don't add to bootloader (reverts on reboot)
sudo nixos-rebuild test --flake .#thinkpad
# Build and switch (activate + set as boot default)
sudo nixos-rebuild switch --flake .#thinkpadThe system tracks the stable NixOS release (nixos-26.05), with home-manager on the matching release-26.05 branch. A handful of fast-moving packages (currently claude-code and codex) come from nixos-unstable instead, via unstableOverlay in flake.nix. Every input is pinned to a specific commit via flake.lock. Nothing changes on your running system until you rebuild.
# Update all flake inputs (nixpkgs, nixpkgs-unstable, home-manager, dotfiles)
nix flake update
# Or update only the unstable packages (e.g. to pick up a new claude-code)
nix flake update nixpkgs-unstable
# Or update only the stable base
nix flake update nixpkgsTo move another package to unstable, add it to unstableOverlay in flake.nix.
Releases come out every May and November. To upgrade, change the nixos-YY.MM branch on the nixpkgs input and the release-YY.MM branch on the home-manager input together, then run nix flake lock and rebuild. Don't change system.stateVersion as part of a release upgrade. Prefer nixos-rebuild boot plus a reboot over switch, since core components like systemd change version.
After updating flake.lock, build without activating, then use nvd to see a version diff (similar to apt-get -s upgrade):
nixos-rebuild build --flake .#thinkpad
nvd diff /run/current-system resultTo see why a package changed, scripts/whatchanged.sh takes each upgrade nvd lists (or the package names you pass it) and prints what on the system depends on it plus the nixpkgs commits between the old and new locked revisions that mention it, with PR links. The PR body is usually where the upstream changelog is linked. It compares flake.lock in HEAD against the working tree, so run it before committing the lock update:
scripts/whatchanged.sh # every version change
scripts/whatchanged.sh boringssl # one packageIf you're happy with the changes:
sudo nixos-rebuild switch --flake .#thinkpadTo roll back if something breaks:
sudo nixos-rebuild switch --rollbackPackages defined in pkgs/ are pinned to an upstream version with one or more content hashes. These hashes are not the same as anything GitHub displays — Nix unpacks the source and re-hashes it in NAR (Nix archive) format, so you can't just paste a release SHA from a GitHub release page.
Most packages have at least:
- A source
hashinsidefetchFromGitHub/fetchurl/ etc. — hash of the unpacked source tree. - A dependency-closure hash, named per ecosystem:
- Go:
vendorHash(vendored Go modules) - Rust:
cargoHash/cargoLock - Node:
npmDepsHash,pnpmDeps.hash,yarnDeps.hash - Python: varies by builder
- Go:
The dependency-closure hash changes whenever the lockfile (go.sum, Cargo.lock, etc.) changes upstream, even across patch releases.
Two workflows, pick whichever:
1. nix-update (easiest): automates version bump + hash recompute.
nix-update --flake --version 0.2.2 agentpen2. Fake-hash dance (manual, always works): edit the package file, bump version, replace each hash with lib.fakeHash (add lib to the function args if needed), then rebuild. Nix will fail with the actual hash in the error message — paste it back in. Repeat for each hash field.
src = fetchFromGitHub {
# ...
rev = "v${version}";
hash = lib.fakeHash; # rebuild, copy real hash from error, paste here
};
vendorHash = lib.fakeHash; # same danceThen rebuild as normal:
sudo nixos-rebuild switch --flake .#thinkpadnix-workstation/
├── flake.nix # Root flake (nixpkgs, home-manager, dotfiles inputs)
├── flake.lock # Pinned input versions
└── hosts/
└── thinkpad/
├── default.nix # Host entry point (hostname, imports)
├── configuration.nix # System config (packages, services, users)
└── hardware-configuration.nix # Hardware-specific (generated, machine-specific)
- The dotfiles flake input is
github:cwage/dotfiles. Use--override-input dotfiles path:...(see "Dotfiles changes" above) to test local edits before pushing. - Home Manager symlinks dotfiles into the Nix store, so dotfile edits require a rebuild to take effect.
hardware-configuration.nixis machine-specific and generated bynixos-generate-config.