Declarative NixOS configuration for poligle@thinkpad, based on Flakes, Home Manager and Stylix.
Desktop environment: Hyprland (configured natively in Nix using the Lua backend due to the recent update),
with the whole colour scheme generated from the current wallpaper.
This repository holds the complete, reproducible configuration of my NixOS system. Everything — packages, services, graphical environment, dotfiles, scripts and theming — is defined declaratively, so the whole system can be rebuilt identically on any machine.
Migrated from Arch Linux + Hyprland (hyprlang) previous repository to NixOS + Hyprland (Lua).
nixos-config/
├── flake.nix # Inputs (nixpkgs, home-manager, stylix) and outputs (hosts)
├── flake.lock # Pinned exact versions (reproducibility)
├── home.nix # Home Manager index (imports home/*)
│
├── hosts/ # Per-machine configuration
│ └── thinkpad/
│ ├── default.nix # Imports modules + hostname + stateVersion
│ └── hardware-configuration.nix
│
├── modules/ # Shared system modules (common across hosts)
│ ├── boot.nix # systemd-boot (UEFI)
│ ├── plymouth.nix # Boot splash, silent boot
│ ├── network.nix # NetworkManager, Bluetooth, rtw89 workaround
│ ├── locale.nix # Timezone, language, keyboard
│ ├── audio.nix # PipeWire
│ ├── hardware.nix # Touchpad, fprintd (off with the lid closed), zram, LED
│ ├── users.nix # User and shell (zsh)
│ ├── packages.nix # System packages, fonts, unfree
│ ├── hyprland.nix # Hyprland compositor
│ ├── regreet.nix # Display manager (greetd + ReGreet)
│ ├── desktop.nix # Thunar, gvfs, tumbler, dconf
│ ├── storage.nix # udisks2 + polkit rule (internal drives without password)
│ ├── services.nix # SSH and other services
│ ├── stylix.nix # Wallpaper, palette, fonts, cursor, opacity
│ ├── nix.nix # Flakes, garbage collection,
│ └── virtualisation.nix # Docker, arm64 (deactivated rn)
│
├── home/ # User configuration (Home Manager)
│ ├── hyprland.nix # Hyprland (Lua): binds, gestures, animations, rules
│ ├── waybar.nix # Status bar (custom CSS driven by the Stylix palette)
│ ├── wofi.nix # Launcher (custom CSS driven by the Stylix palette)
│ ├── kitty.nix # Terminal
│ ├── hyprlock.nix # Lock screen (password + fingerprint)
│ ├── hypridle.nix # Idle daemon
│ ├── dunst.nix # Notifications / OSD
│ ├── awww.nix # Wallpaper daemon
│ ├── gtk.nix # GTK settings and icon theme
│ ├── thunar.nix # XFCE helper so Thunar opens kitty
│ ├── desktop-entries.nix # Hides unwanted launcher entries
│ ├── zsh.nix # Shell
│ ├── zen.nix # Zen Browser
│ ├── eww.nix # Widgets
│ ├── nvim.nix # Editor
│ ├── vscode.nix # Editor
│ ├── python.nix # Python environment
│ ├── scripts.nix # Own scripts, packaged
│ ├── wifi-menu.sh # Wofi Wi-Fi menu (right click on the waybar icon)
│ ├── bluetooth.nix # Blueman without auto power-on
│ ├── engineering.nix # FHS environment + launchers for MATLAB, FEKO and ADS
│ ├── cli.nix # CLI tooling (gh, lazygit, fzf, bat, tealdeer)
│ ├── refresh.nix # Reloads Hyprland, waybar and eww after each switch
│ └── waybar-autohide.py # Python auto-hide script for the bar
│
└── wallpapers/ # Wallpapers (versioned for cloning)
hosts/— What is specific to each machine (hardware, hostname).modules/— What is common to all machines (packages, services, environment).home/— The user configuration (declarative dotfiles).
Adding a new machine is just a matter of creating a hosts/<name>/ that imports the
same shared modules plus its own hardware-configuration.nix.
Colours are generated from the wallpaper by Stylix and applied system-wide: GTK, Qt, kitty, dunst, hyprlock, Hyprland borders, VSCode, Plymouth, ReGreet and Zen.
modules/stylix.nix is the single source of truth. The wallpaper is declared once
there and everything else references it — awww.nix reads config.stylix.image to set
the desktop background, and hyprlock gets its background from Stylix directly. Wallpaper,
lock screen and palette cannot drift apart by construction.
# modules/stylix.nix
image = ../wallpapers/some-other.jpg;Then rebuild. A reboot is the simplest way to see everything applied at once, since waybar is launched by Hyprland rather than systemd and won't reload on its own.
The generated palette can be inspected at /etc/stylix/palette.html before committing
to a wallpaper. More colourful images tend to produce better palettes.
- waybar and wofi keep their own CSS (
stylix.targets.*.enable = false), but their colours are pulled fromconfig.lib.stylix.colors. This preserves the custom layout — rounded module groups, icon-only launcher grid — while still following the wallpaper. - Icons stay on Colloid regardless of the palette (
stylix.iconThemeis opt-in and left off). - Obsidian is not themed: Stylix has no target for it.
Rebuild the system after editing the configuration:
sudo nixos-rebuild switch --flake ~/nixos-config#<name> # or with the alias nrebuildnh os switch does the same and also prints the package diff between generations.
Validate the configuration without building:
nix flake check # or with the alias ncheckBuild without activating (to test changes safely):
sudo nixos-rebuild build --flake ~/nixos-config#<name>Update packages (equivalent to pacman -Syu from Arch):
nix flake update # or with the alias nupdate
sudo nixos-rebuild switch --flake ~/nixos-config#thinkpadManually clean up old generations:
sudo nix-collect-garbage --delete-older-than 7d # or with the alias ngcGarbage collection also runs automatically on a weekly basis (see
modules/nix.nix).
- System won't boot: pick an earlier generation from the boot menu (systemd-boot). It's a system-level restore point.
- A configuration broke something: roll the repository back to a previous commit
with Git (
git log,git revert,git checkout), fix it, and rebuild. Git versions the code; generations version the system.
-
Install base NixOS and generate the new machine's hardware config:
sudo nixos-generate-config --show-hardware-config > /tmp/hardware-configuration.nix -
Clone this repository:
git clone git@github.com:poligle/nixos-config cd nixos-config -
Create the new host:
mkdir -p hosts/<name> cp /tmp/hardware-configuration.nix hosts/<name>/ cp hosts/thinkpad/default.nix hosts/<name>/default.nix # edit hosts/<name>/default.nix: adjust networking.hostName
-
Add the new configuration to
flake.nix:nixosConfigurations.thinkpad = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ ./hosts/thinkpad/default.nix stylix.nixosModules.stylix home-manager.nixosModules.home-manager { home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; home-manager.extraSpecialArgs = { inherit inputs; }; home-manager.users.poligle = ./home.nix; } ]; };
-
Build:
git add . sudo nixos-rebuild switch --flake .#<name>
Local state is not part of the repository and has to be set up again on each machine:
user password (passwd), SSH keys, fingerprint enrollment (fprintd-enroll) and the
engineering installs (~/eda, ~/.config/eda/licenses.env).
- Filesystem: btrfs with subvolumes (
@,@home,@nix,@log), zstd compression andnoatime. Swap via zram. - Boot: UEFI + systemd-boot, Plymouth splash with silent boot kernel parameters.
- Login: greetd + ReGreet running in cage on the last connected monitor, themed by
Stylix. The Spanish layout reaches cage through
XKB_DEFAULT_LAYOUT. - Hyprland in Lua: since version 0.55, Hyprland recommends Lua configuration. The
whole config is declared in Nix (
wayland.windowManager.hyprland.settings) and Home Manager generateshyprland.lua: every attribute becomes anhl.<name>(...)call, lists become one call per element,_argsgives multi-argument calls and inline Lua goes throughlib.generators.mkLuaInline. Stylix also writes intosettings.config, so keys it already sets (border and shadow colours) needlib.mkForce. Under Lua,hyprctl dispatchtakes Lua too:hyprctl dispatch 'hl.dsp.dpms({ action = "off" })'. - Pre-existing files: if a file Home Manager wants to manage already exists, it is
renamed to
*.hm-backupinstead of aborting the whole activation. - Live refresh: after every switch,
home/refresh.nixreloads Hyprland and restarts waybar (with its auto-hide) and the eww widgets inside the running session, because none of them notice that their config symlink now points to a new store path. - Own scripts: packaged with
writeShellScriptBinand their dependencies declared explicitly, for full reproducibility:waybar-autohide,mic-led-sync,osd-volume,osd-brightness,wifi-menu,wifi-status,bluetooth-status. - Engineering software: GNU Octave and Python (numpy, scipy, matplotlib, scikit-rf)
are declarative. MATLAB, Altair FEKO and Keysight ADS can't be packaged (licensing,
opaque dependencies), so
home/engineering.nixprovideseda-fhs, an FHS sandbox with the libraries they expect, plusmatlab,fekoandadslaunchers and menu entries. Each one is installed from insideeda-fhsinto~/eda/{matlab,altair,keysight/ads}; licence servers go in~/.config/eda/licenses.env, which stays out of the repo.
Public repository: contains no secrets or credentials. User passwords are set with
passwd, and SSH keys and fingerprint data live outside the repository.


