Mój osobisty config NixOS, modularny, oparty o flake-parts + home-manager + plasma-manager + disko.
.
├── flake.nix # wejście - inputs, outputs przez flake-parts + import-tree
├── flake.lock
├── guides/ # notatki dla przyszłego-mnie (install, post-install)
└── modules/
├── meta.nix # wspólne opcje: my.username, my.stateVersion itd.
├── hardware/ # moduły sprzętowe (GPU, bluetooth, drukarki...)
│ └── nvidia.nix
├── nixos/ # moduły systemowe (services.*, programs.* na poziomie systemu)
│ ├── base.nix
│ ├── boot.nix
│ ├── networking.nix
│ ├── users.nix
│ ├── audio.nix
│ ├── plasma.nix # Plasma jako DE (SDDM + plasma6)
│ ├── ram-settings.nix
│ └── packages-common.nix # bazowe CLI bez configu
├── home/ # moduły home-manager (programs.*, user-scoped)
│ └── plasma.nix # plasma-manager: deklaratywny config plasmy usera
├── home-manager.nix # podpięcie HM jako NixOS module + globalne imports
└── hosts/
├── _common.nix # lista modułów wspólna dla wszystkich hostów
├── pc/
│ ├── default.nix
│ ├── _hardware.nix
│ └── _disko.nix
└── laptop/
└── default.nix
Pliki zaczynające się od _ nie są modułami flake-parts i nie pojawiają się jako self.nixosModules.* / self.homeModules.*. Są to:
- Host-specific pliki (
_hardware.nix,_disko.nix) - importowane bezpośrednio przezdefault.nixhosta - Aggregatory (
_common.nix) - zwracają listę modułów do użycia wmodules = [...]
Pliki bez underscore to moduły flake-parts, automatycznie rejestrowane przez import-tree i dostępne jako self.nixosModules.<nazwa> / self.homeModules.<nazwa>.
{ self, inputs, ... }: { # warstwa flake-parts (rejestracja)
flake.nixosModules.boot = { ... }: { # warstwa NixOS (właściwy config)
boot.loader.systemd-boot.enable = true;
};
}- Zewnętrzna funkcja - args od flake-parts (
self,inputs,configflake-parts) - Wewnętrzna funkcja - args od NixOS-a (
pkgs,configsystemu,lib)
To są dwie różne przestrzenie - inputs dostępny w zewnętrznej, ale w wewnętrznej już nie (chyba że przez closure).
Pytanie: czy to user-specific czy system-wide?
| Przypadek | Gdzie |
|---|---|
| GUI apka dla mnie (firefox, discord, telegram) | modules/home/packages.nix¹ → home.packages |
| CLI dla mojego usera (opencode, yt-dlp) | modules/home/packages.nix¹ → home.packages |
| Narzędzie systemowe (fsck, nmap, tcpdump) | modules/nixos/packages-common.nix → environment.systemPackages |
| Dostępne przed zalogowaniem / dla root | modules/nixos/packages-common.nix |
¹
modules/home/packages.nixto plik do stworzenia - jeszcze go nie ma w repo. Szablon:{ ... }: { flake.homeModules.packages = { pkgs, ... }: { home.packages = with pkgs; [ firefox telegram-desktop discord # itd. ]; }; }Po stworzeniu dodaj
self.homeModules.packagesdo globalnych imports wmodules/home-manager.nix.
Użyj dedykowanego modułu programs.X / services.X zamiast ręcznie dorzucać paczkę + dotfile'y.
| Przypadek | Gdzie | Jak |
|---|---|---|
| User tool z configiem (git, neovim, tmux, zsh) | modules/home/<nazwa>.nix |
flake.homeModules.<nazwa> z programs.X |
| Systemowa usługa (nginx, postgres, docker) | modules/nixos/<nazwa>.nix |
flake.nixosModules.<nazwa> z services.X / programs.X |
Ważne:
programs.git.enable = truew HM instaluje git + generuje config. Nie musisz dodawaćgitdohome.packagesani dopackages-common.nix. Dublowanie = problemy.
→ modules/hardware/<nazwa>.nix (np. nvidia.nix, bluetooth.nix, printer-brother.nix)
→ modules/hosts/<host>/default.nix, w sekcji "moduły tylko dla tego hosta" albo inline overrides.
users.${config.my.username} = {
imports = [
inputs.plasma-manager.homeModules.plasma-manager # dependency
self.homeModules.git # mój globalny moduł
self.homeModules.neovim # mój globalny moduł
];
...
};({ config, ... }: {
home-manager.users.${config.my.username}.imports = [
self.homeModules.plasma # tylko PC ma KDE
];
})Reguła: "czy chcę to na każdej maszynie?" → tak: globalne, nie: host-specific. Nie dubluj tego samego modułu w obu miejscach.
Przykład z plasmą który często myli:
# modules/nixos/plasma.nix
services.desktopManager.plasma6.enable = true; # "zainstaluj KDE jako DE na systemie"
# modules/home/plasma.nix
programs.plasma.enable = true; # "zarządzaj moimi ustawieniami plasmy przez plasma-manager"To nie jest duplikacja. Pierwsze = fizyczna obecność Plasmy (pakiety, sesja SDDM, dbus). Drugie = plasma-manager deklaratywnie zapisuje moje programs.plasma.* do ~/.config/.
Analogicznie dla innych programów: NixOS-owy programs.X.enable i HM-owy programs.X.enable to zazwyczaj różne rzeczy - jedno systemowe, drugie user-scoped.
- Stwórz
modules/home/git.nix:{ ... }: { flake.homeModules.git = { ... }: { programs.git = { enable = true; userName = "..."; userEmail = "..."; }; }; }
- Dodaj do
importswmodules/home-manager.nix:imports = [ inputs.plasma-manager.homeModules.plasma-manager self.homeModules.git # ← nowe ];
sudo nixos-rebuild switch --flake .#<host>(np..#pc)
- Stwórz
modules/nixos/docker.nix:{ ... }: { flake.nixosModules.docker = { ... }: { virtualisation.docker.enable = true; }; }
- Jeśli chcę go na każdej maszynie → dodaj do
modules/hosts/_common.nix - Jeśli tylko na wybranej → dodaj do
modules/hosts/<host>/default.nixw sekcji moduły specyficzne
- Stwórz
modules/hosts/<nazwa>/default.nix(wzorując się napc/) - Skopiuj strukturę -
commonModulesz_common.nix+ host-specific moduły + inline overrides - Opcjonalnie dodaj
_hardware.nix/_disko.nixdla tego hosta
Jeśli używam programs.git.enable = true, nie dodaję git dodatkowo do home.packages ani environment.systemPackages. Moduł programs.X już to robi.
# źle:
home-manager.users.mrcapi.imports = [...];
# dobrze:
home-manager.users.${config.my.username}.imports = [...];Jedno źródło prawdy w meta.nix. Zmiana username w jednym miejscu.
W module flake-parts self, inputs są w zewnętrznej funkcji. W wewnętrznej (właściwym NixOS module) są pkgs, config (systemu), lib. Jak potrzebuję inputs w wewnętrznej - capture'uję przez closure z zewnętrznej funkcji.
# ŹLE - "inputs" nie jest argumentem wewnętrznej funkcji, crash przy evalu
{ ... }: {
flake.nixosModules.foo = { pkgs, inputs, ... }: {
# ^^^^^^ nie istnieje tutaj
environment.systemPackages = [ inputs.some-flake.packages.${pkgs.system}.default ];
};
}
# DOBRZE - "inputs" pobrany w zewnętrznej funkcji, wewnętrzna używa przez closure
{ inputs, ... }: {
# ^^^^^^ tutaj dostajemy inputs od flake-parts
flake.nixosModules.foo = { pkgs, ... }: {
environment.systemPackages = [ inputs.some-flake.packages.${pkgs.system}.default ];
# ^^^^^^ działa, bo zmienna jest "widoczna" z zewnętrznej funkcji
};
}Ta sama zasada działa dla self - dostajesz go w zewnętrznej funkcji ({ self, ... }:) i używasz w wewnętrznej przez closure. W hostach jest inaczej, bo tam jawnie przekazujesz self/inputs przez specialArgs = { inherit self inputs; } - wtedy są dostępne w każdym module.
Nie rozbijam paczek bez configu na osobne pliki (modules/home/htop.nix z 3 linijkami). Worek packages.nix wystarczy dopóki paczka nie zacznie mieć realnego configu - wtedy "awansuje" do dedykowanego pliku.
Jeśli zmieniam podejście w configu - aktualizuję też ten README i komentarze w plikach. Martwe komentarze są gorsze niż brak komentarzy, bo aktywnie wprowadzają w błąd.
# Rebuild wybranego hosta (po # podajesz nazwę z flake.nixosConfigurations.*)
sudo nixos-rebuild switch --flake .#pc
sudo nixos-rebuild switch --flake .#laptop
# Test bez aktywacji (build + aktywacja bez wpisu w bootloaderze)
sudo nixos-rebuild test --flake .#pc
# Sprawdź co się zmieni (tylko build, bez aktywacji)
sudo nixos-rebuild dry-activate --flake .#pc
# Shortcut: bez #host bierze hostname z aktualnej maszyny
sudo nixos-rebuild switch --flake .
# Aktualizacja inputs
nix flake update
# Aktualizacja pojedynczego inputa
nix flake lock --update-input nixpkgs
# Sprawdzenie flake'a (eval, check)
nix flake check- Sekrety (sops-nix albo agenix) - na razie brak, czas pomyśleć zanim pchnę coś wrażliwego
- Rozdzielenie profilu Git (prywatny vs pracowniczy) przez
programs.git.includes - Ewentualne wprowadzenie
wrapper-modules- dopiero jak realnie będzie potrzebna portability