Fully declarative NixOS + Home Manager configuration for a Hyprland desktop, themed Catppuccin Mocha. Migrated from an Arch/Hyprland dotfiles setup and rewritten as pure Nix (no live-symlinked dotfile tree).
Hosts:
thinkpad-x1-carbon-g7— ThinkPad X1 Carbon (7th Gen).thinkpad-x1-carbon-g12— ThinkPad X1 Carbon (Gen 12, 21KC; Intel Core Ultra 5 125U / Meteor Lake, btrfs root).
Both hosts share one system module (hosts/common.nix) and the same Home Manager
config; each only adds its own generated hardware-configuration.nix (plus, for
the Gen 12, the Meteor Lake iGPU video stack).
| Area | Module | Approach |
|---|---|---|
| System (boot, audio, login, fonts, fcitx5, fingerprint, …) | hosts/common.nix (shared) + hosts/<host>/configuration.nix |
NixOS options |
| Compositor + keybindings | home/hyprland.nix |
wayland.windowManager.hyprland.settings (all binds inlined) |
| Status bar | home/waybar.nix |
programs.waybar.settings + readFile style.css |
| Lock / idle / wallpaper | home/desktop.nix |
programs.hyprlock · services.hypridle · services.hyprpaper |
| Launcher / menus / OSD | home/desktop.nix |
programs.wofi + rofi/wlogout/swayosd files |
| Terminal | home/kitty.nix |
programs.kitty (+ themeFile = "Catppuccin-Mocha") |
| Shell / prompt | home/shell.nix |
bash + programs.starship |
| Scripts + timers | home/scripts.nix |
in-repo scripts + systemd user timers |
| Cursor / GTK / icons / Qt | home/theming.nix |
home.pointerCursor · gtk · qt |
Structured configs are converted to native Nix attribute sets. Opaque blobs that
have no attribute-set form — CSS, rofi .rasi, kanata .kbd, the starship TOML,
shell scripts, images — live under home/files/ and are referenced from
Nix (readFile / .source / importTOML). That keeps the repo self-contained
and the deployment fully declarative.
flake.nix # inputs + per-user vars + `hosts` list → one config each
hosts/
common.nix # shared system config (imported by every host)
thinkpad-x1-carbon-g7/
configuration.nix # imports ../common.nix + hardware
hardware-configuration.nix # PLACEHOLDER — regenerate on the machine
thinkpad-x1-carbon-g12/
configuration.nix # ../common.nix + Meteor Lake iGPU video stack
hardware-configuration.nix # PLACEHOLDER — regenerate on the machine
home/
home.nix hyprland.nix waybar.nix kitty.nix shell.nix
desktop.nix scripts.nix theming.nix
starship.toml
files/ # CSS, rasi, scripts, icons, backgrounds, …
The config is parameterized — to adopt it you don't need to find-and-replace a
username. Edit the two per-user values at the top of the let block in
flake.nix, then add (or rename) a host in the hosts list:
username = "maxime"; # your login name → home dir becomes /home/<username>
fullName = "Maxime Gagne"; # account description
hosts = [
"thinkpad-x1-carbon-g7"
"thinkpad-x1-carbon-g12"
# "<your-hostname>" # ← add yours; create a matching hosts/<your-hostname>/
];Each entry builds nixosConfigurations.<name> (via lib.genAttrs), sets
networking.hostName, and reads hosts/<name>/. To add a machine, copy an
existing host dir (e.g. cp -r hosts/thinkpad-x1-carbon-g7 hosts/<name>), add the
name to the list, and regenerate its hardware-configuration.nix on the box.
home.homeDirectory, the NixOS user (users.users.${username}), and the flake's
host path all derive from the variables; runtime config paths use ~, so they
need no edits.
A from-scratch walkthrough — from NixOS install media to this flake running on the
machine. Commands assume the Gen 12 (thinkpad-x1-carbon-g12: UEFI, NVMe,
btrfs root); set HOST=thinkpad-x1-carbon-g7 for the 7th Gen. The committed
hardware-configuration.nix files are placeholders (not bootable) that exist
only so nix flake check evaluates — you regenerate the real one in step 5.
⚠️ Step 3 erases the target disk. Confirm the device name withlsblkbefore running anyparted/mkfscommand. Official manual: https://nixos.org/manual/nixos/stable/#sec-installation.
The installer logs you in as the unprivileged
nixosuser, so every command that touches the disk,/mnt, or the system is prefixed withsudobelow. Prefer this oversudo -iso yourDISK/HOSTshell vars stay set. One catch: a>redirect runs asnixos(not root) and fails on root-owned paths, so the hardware-config step pipes tosudo teeinstead (step 4).
Download the Minimal ISO (x86_64) from https://nixos.org/download/ — direct
link https://channels.nixos.org/nixos-26.05/latest-nixos-minimal-x86_64-linux.iso
(the Graphical ISO works too). Verify the SHA-256 shown on the download page, then
write it to a USB stick — replace /dev/sdX with the stick's device (not a
partition, and not your internal disk):
sudo dd if=nixos-minimal-*.iso of=/dev/sdX bs=4M status=progress oflag=syncReboot, tap F12 for the ThinkPad boot menu (or F1 for firmware), and boot the USB. If it refuses, disable Secure Boot in firmware first.
NetworkManager is running; connect Wi-Fi from the console with nmtui (works in a
non-graphical session):
sudo nmtui # Activate a connection → choose your SSID → enter password
ping -c1 nixos.org # confirm connectivityHandy for doing the rest from another machine (copy-paste, a real terminal). The
installer logs in as the nixos user, which has no password and so can't SSH in
yet — set one, then find the laptop's IP:
passwd # set a password for the 'nixos' user (sshd is already running)
ip -c a # note the wlan IP, e.g. 192.168.2.31From your other machine, connect as nixos and continue with the steps below:
ssh nixos@<IP> # e.g. ssh nixos@192.168.2.31nixos-install --flake needs the flakes feature turned on:
export NIX_CONFIG="experimental-features = nix-command flakes"Identify the disk with lsblk — the X1's NVMe is usually /dev/nvme0n1, whose
partitions are suffixed p1, p2, …. Set DISK to match, then:
DISK=/dev/nvme0n1
sudo parted $DISK -- mklabel gpt
sudo parted $DISK -- mkpart ESP fat32 1MB 1GB # EFI system partition
sudo parted $DISK -- set 1 esp on
sudo parted $DISK -- mkpart nixos 1GB 100% # root fills the rest
sudo mkfs.fat -F 32 -n boot ${DISK}p1
sudo mkfs.btrfs -L nixos ${DISK}p2Create a btrfs @ subvolume (matches the subvol=@ in the host's hardware
placeholder) and mount everything under /mnt:
sudo mount ${DISK}p2 /mnt
sudo btrfs subvolume create /mnt/@
sudo umount /mnt
sudo mount -o subvol=@,compress=zstd,noatime ${DISK}p2 /mnt
sudo mkdir -p /mnt/boot
sudo mount -o umask=077 ${DISK}p1 /mnt/bootOptional swap: add a
@swapsubvolume + swapfile (or a swap partition) andswaponit —nixos-generate-configrecords whatever it finds.
HOST=thinkpad-x1-carbon-g12 # or: thinkpad-x1-carbon-g7
nix-shell -p git
sudo git clone https://github.com/max8989/nixos-dotfiles /mnt/etc/nixos/nixos-dotfiles
cd /mnt/etc/nixos/nixos-dotfiles
sudo nixos-generate-config --root /mnt --show-hardware-config \
| sudo tee hosts/$HOST/hardware-configuration.nix > /dev/null # `sudo tee`, not `>` (redirect runs as nixos)
⚠️ Verify the file was actually written before staging it. A plain>redirect runs as the unprivilegednixosuser and silently fails on the root-owned tree (Permission denied) — which leaves the committed placeholder in place. If you thengit addand install, the placeholder'sext4root never mounts and the machine drops to emergency mode on first boot (Timed out waiting for device /dev/disk/by-label/nixos). Confirm the real values landed:grep -E 'fsType|subvol|by-uuid' hosts/$HOST/hardware-configuration.nixYou must see
fsType = "btrfs", anoptions = [ "subvol=@" ];line, and aby-uuiddevice — notext4orby-label. Only then:
sudo git add hosts/$HOST/hardware-configuration.nix # flakes only see git-tracked filessystem.stateVersion(hosts/common.nix) andhome.stateVersion(home/home.nix) → preset to26.05(the current ISO). Only change these if you install a different release, and never bump them after install.time.timeZone(currentlyAmerica/Toronto) andi18n.defaultLocale.- Confirm the generated
hardware-configuration.nixshows your real filesystems.
sudo NIX_CONFIG="experimental-features = nix-command flakes" \
nixos-install --flake .#$HOST # prompts for the root password at the end
sudo nixos-enter --root /mnt -c 'passwd <username>' # set your login user's password
sudo rebootRemove the USB and boot. Log in at tuigreet → Hyprland as your user, then
enroll the fingerprint reader with fprintd-enroll. From here on, apply changes
with the rebuild command below.
The installer clones it to /etc/nixos/nixos-dotfiles (that's
/mnt/etc/nixos/... during install). That copy is root-owned, which is awkward
for day-to-day edits, so the common pattern is to keep a working clone in your
home and rebuild from there — flakes build from any path, the location is not
special:
git clone https://github.com/max8989/nixos-dotfiles ~/repos/nixos-dotfiles
cd ~/repos/nixos-dotfilesPick one home for the repo and rebuild from it consistently (the examples below
use ~/repos/nixos-dotfiles). The only hard rule is that the flake can only see
git-tracked files inside the repo, so git add new files before rebuilding.
cd ~/repos/nixos-dotfiles
nix flake check # evaluate both hosts first
sudo nixos-rebuild switch --flake .#thinkpad-x1-carbon-g12 # or .#thinkpad-x1-carbon-g7switch builds the new generation and activates it immediately. Use
boot instead of switch to apply only on next reboot, or test to activate
without making it the boot default.
nix build .#nixosConfigurations.thinkpad-x1-carbon-g12.config.system.build.vm
./result/bin/run-thinkpad-x1-carbon-g12-vmWhere a package goes depends on what it is. Find the binary you want first
(nix search nixpkgs <name> or https://search.nixos.org/packages), then add the
attribute name to the right list:
| What you're adding | Where | How |
|---|---|---|
A user app or CLI tool (browsers, editors, ripgrep, …) |
home/home.nix → home.packages |
add the attr to the list |
| A system service / daemon (docker, flatpak, firewall, printing, …) | hosts/common.nix |
use its NixOS option, e.g. services.<name>.enable = true; |
| A font | hosts/common.nix → fonts.packages |
add the attr to the list |
| Something only one machine needs (GPU drivers, kernel modules) | hosts/<host>/configuration.nix |
per-host option |
Most of the time you want the first row. For example, to add neofetch:
# home/home.nix — inside home.packages = with pkgs; [ … ];
home.packages = with pkgs; [
# …existing…
neofetch
];Then evaluate and apply:
cd ~/repos/nixos-dotfiles
nix flake check # catch typos/renamed attrs early
nixfmt **/*.nix # keep formatting consistent
sudo nixos-rebuild switch --flake .#thinkpad-x1-carbon-g12 # or -g7Notes:
- Unfree packages (Chrome, Spotify, VS Code, …) already work —
common.nixsetsnixpkgs.config.allowUnfree = true;. - No native package? Check if it's on Flathub —
services.flatpakis enabled (add a remote once:flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo), or for browser-launched binaries see the zen-browser flake input as a model. - Don't
pip install/npm -g/ drop binaries in~/.local/binand expect them to persist — on NixOS the declarative list is the source of truth.
nix flake check now passes clean on both hosts against the pinned flake.lock,
so the attribute/option names below are confirmed present there. They're kept as
a checklist for when you bump nixpkgs/home-manager (re-run nix flake check
after any input update and fix anything that has since moved):
pkgs.catppuccin-gtk— recent nixpkgs may expose it aspkgs.catppuccin.gtkTheme.pkgs.figtree— may live undergoogle-fonts.pkgs.nerd-fonts.caskaydia-cove/pkgs.nerd-fonts.jetbrains-mono(post nerd-fonts restructure).pkgs.zed-editor,pkgs.swayosd,pkgs.swaynotificationcenter.i18n.inputMethod.type = "fcitx5"(newer form; older nixpkgs usedenabled = "fcitx5").- HM service modules used here:
services.hypridle,services.hyprpaper,programs.hyprlock,programs.wofi,programs.waybar.systemd. inputs.zen-browser.packages.<system>.default.programs.kitty.themeFile = "Catppuccin-Mocha"(name frompkgs.kitty-themes).
- Single theme. The Arch setup had a runtime 6-theme switcher (it copied config files into place). Pure Nix puts configs in the immutable store, so the switcher is dropped — Catppuccin Mocha is baked in declaratively. The other themes' CSS/jsonc were not ported.
hyprswitchremoved. Upstream renamed it tohyprshellwith a different CLI, so the old Alt-Tab binds/exec-oncewould break. They're dropped; re-add via thehyprshellflake + new CLI if you want the switcher.hyprwat(SUPER+F12 / waybar audio click) is dead. It's AUR-only and not in nixpkgs.pavucontrolandwpctlcover audio selection until it's packaged.- Daemon autostart.
hyprpaper/hypridle/waybarrun as Home Manager systemd user services (ongraphical-session.target);swaync/swayosd-serverare still launched from Hyprlandexec-once. If something doesn't start, checksystemctl --user status <name>. wlogout/layoutactions point at~/.config/hypr/scripts/power.sh(a path inherited from the Arch dotfiles); the scripts tree deploys to~/.config/scripts. Adjust if you use the wlogout menu directly.system-update.shis Arch-only and self-exits on NixOS (harmless).- Neovim is out of scope (no longer used — not ported).