From da32fc7529782c76a6610db31e46480f3fd97697 Mon Sep 17 00:00:00 2001 From: Love Billenius Date: Tue, 24 Mar 2026 17:00:49 +0100 Subject: [PATCH] nix --- README.md | 316 ++++++++++++++++++++++++ flake.nix | 25 ++ hosts/kronos/default.nix | 66 +++++ hosts/kronos/disko.nix | 118 +++++++++ hosts/kronos/hardware-configuration.nix | 3 + modules/base.nix | 103 ++++++++ modules/desktop-hyprland.nix | 113 +++++++++ modules/zfs-root.nix | 28 +++ 8 files changed, 772 insertions(+) create mode 100644 README.md create mode 100644 flake.nix create mode 100644 hosts/kronos/default.nix create mode 100644 hosts/kronos/disko.nix create mode 100644 hosts/kronos/hardware-configuration.nix create mode 100644 modules/base.nix create mode 100644 modules/desktop-hyprland.nix create mode 100644 modules/zfs-root.nix diff --git a/README.md b/README.md new file mode 100644 index 0000000..2362780 --- /dev/null +++ b/README.md @@ -0,0 +1,316 @@ +# NixOS-konfiguration + +Det här förrådet innehåller en flake-baserad NixOS-konfiguration för mina +maskiner. + +Just nu finns hosten `kronos`, med: + +- `Hyprland` +- `ZFS` som root-filsystem +- `LUKS` under `ZFS` +- `TPM2` för automatisk upplåsning av LUKS +- `zram` i stället för swap-partition +- `systemd-networkd` och `systemd-resolved` +- `GRUB` med `os-prober` for Windows-stöd +- proprietär `NVIDIA`-drivrutin för `kronos` +- `OpenSSH` +- automatiska `ZFS`-snapshots + +Dotfiles hanteras inte av Nix i det här förrådet. De installeras separat med +`stow` från `~/dotfiles`. + +## Struktur + +- `flake.nix` är startpunkten +- `hosts/kronos/default.nix` är huvudfilen för maskinen `kronos` +- `hosts/kronos/disko.nix` beskriver disk-layouten +- `hosts/kronos/hardware-configuration.nix` är hårdvaruspecifik och genereras på maskinen +- `modules/base.nix` innehåller grundsystem +- `modules/zfs-root.nix` innehåller boot, LUKS, TPM och ZFS-relaterade delar +- `modules/desktop-hyprland.nix` innehåller desktopmiljön och paketen + +## Viktigt innan installation + +Det finns några saker som måste ändras innan konfigurationen används på en ny maskin. + +1. Sätt rätt disk i `hosts/kronos/default.nix` + - byt ut `REPLACE_ME` mot rätt `/dev/disk/by-id/...` +2. Generera riktig `hardware-configuration.nix` + - den nuvarande filen är bara en platshållare tills den genereras på målsystemet +3. Kontrollera `networking.hostId` + - ZFS kräver att den är satt +4. Tänk på att disken raderas + - `disko` partitionerar, formaterar och skapar LUKS/ZFS enligt konfigurationen +5. `kronos` antar `NVIDIA` + - hosten är nu konfigurerad för `nvidia-open` med modesetting + +Disk-layouten är just nu: + +- `EFI` för boot +- `LUKS` på resten av disken +- `ZFS pool` (`rpool`) ovanpå den upplåsta LUKS-enheten +- ingen vanlig swap-partition på disk +- `zram` används för swap i RAM + +Bootloadern är `GRUB`, inte `systemd-boot`, eftersom `kronos` är satt upp för att +hitta andra installerade system via `os-prober`, till exempel Windows. + +## Installation från minimal NixOS-installationsmedia + +Om nätverket inte redan fungerar: + +```bash +nmtui +``` + +Sedan: + +```bash +sudo -i +export NIX_CONFIG='experimental-features = nix-command flakes' +cd /tmp +git clone nixcfg +cd nixcfg +``` + +Redigera sedan rätt disk i `hosts/kronos/default.nix`: + +```bash +vim hosts/kronos/default.nix +``` + +Applicera disk-layouten: + +```bash +nix run github:nix-community/disko -- --mode disko .#kronos +``` + +Det kommandot gör i praktiken detta: + +- partitionerar disken +- skapar EFI-partitionen +- skapar LUKS-enheten `cryptroot` +- skapar ZFS-poolen `rpool` +- skapar datasets +- monterar målsystemet under `/mnt` + +Det är därför nästa steg använder `/mnt`. + +Generera hårdvarufilen och kopiera in den i förrådet: + +```bash +nixos-generate-config --root /mnt +cp /mnt/etc/nixos/hardware-configuration.nix ./hosts/kronos/hardware-configuration.nix +``` + +Installera systemet: + +```bash +nixos-install --flake .#kronos +``` + +Enrolla sedan TPM-nyckeln innan omstart: + +```bash +nixos-enter --root /mnt -c 'systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/disk/by-partlabel/cryptroot' +``` + +Och starta om: + +```bash +reboot +``` + +## Vad TPM-upplåsningen betyder + +TPM ersätter inte ditt vanliga LUKS-lösenord. Den lägger till en extra väg för +upplåsning. + +Det betyder: + +- LUKS kan fortfarande låsas upp med recovery-lösenord/passphrase +- maskinen kan låsa upp automatiskt med TPM om boot-tillståndet matchar +- om firmware, Secure Boot eller PCR-värden ändras kan du behöva skriva lösenordet manuellt en gång och sedan enrolla om TPM + +## Efter första uppstart + +Kloning av dotfiles och installation med `stow` görs separat: + +```bash +git clone ~/dotfiles +cd ~/dotfiles +stow ghostty foot hyprland paru shell tmux wezterm +``` + +Observera att din nuvarande Hyprland-konfiguration fortfarande försöker starta +`nm-applet`. Eftersom systemet använder `systemd-networkd` i stället för +NetworkManager bör den raden tas bort eller ersättas i dina dotfiles. + +## Vanligt underhåll + +När systemet väl är installerat jobbar man oftast så här. + +Uppdatera flake-låset: + +```bash +nix flake update +``` + +Bygg och applicera ändringar på den lokala maskinen: + +```bash +sudo nixos-rebuild switch --flake .#kronos +``` + +Testa en konfiguration utan att göra den permanent vid nästa boot: + +```bash +sudo nixos-rebuild test --flake .#kronos +``` + +Bygg bara, utan att byta system direkt: + +```bash +sudo nixos-rebuild build --flake .#kronos +``` + +Rensa gamla generationer och oanvända paket: + +```bash +sudo nix-collect-garbage -d +``` + +Kontrollera `ZFS`-snapshots: + +```bash +zfs list -t snapshot +``` + +Kontrollera `OpenSSH`: + +```bash +systemctl status sshd +``` + +Bra arbetssätt är ofta: + +- ändra Nix-filerna +- kör `nixos-rebuild test` eller `switch` +- verifiera att allt fungerar +- committa ändringen i förrådet + +Eftersom NixOS sparar generationer är det också lättare att backa tillbaka än i +många traditionella distributioner. + +## Flera maskiner i samma förråd + +Det här upplägget är gjort för att flera maskiner ska kunna leva i samma förråd. + +En vanlig struktur är: + +- gemensamma moduler i `modules/` +- en mapp per maskin i `hosts//` + +För att lägga till en ny maskin kan du till exempel: + +1. skapa en ny mapp, till exempel `hosts/atlas/` +2. lägga in en egen `default.nix` +3. lägga in en egen `disko.nix` om disk-layouten skiljer sig +4. generera en egen `hardware-configuration.nix` på den maskinen +5. lägga till hosten i `flake.nix` + +Exempel i `flake.nix`: + +```nix +nixosConfigurations = { + kronos = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = { inherit inputs; }; + modules = [ + disko.nixosModules.disko + ./hosts/kronos + ]; + }; + + atlas = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = { inherit inputs; }; + modules = [ + disko.nixosModules.disko + ./hosts/atlas + ]; + }; +}; +``` + +Sedan kan varje maskin byggas separat: + +```bash +sudo nixos-rebuild switch --flake .#kronos +sudo nixos-rebuild switch --flake .#atlas +``` + +Det fina med detta är att gemensamma delar, som `base.nix` eller +`desktop-hyprland.nix`, kan återanvändas, medan varje maskin ändå kan ha egen: + +- disk-layout +- hostname +- hårdvarufil +- användare eller roller +- specialpaket eller tjänster + +## Sammanfattning + +Tanken med förrådet är: + +- ett ställe för systemkonfiguration +- separata hostar per maskin +- återanvändbara moduler för gemensamma delar +- dotfiles separat via `stow` +- enkel reproducerbar installation från minimal NixOS-media + +Observera att NVIDIA-delen just nu är hostspecifik för `kronos`. Om du lägger till +en annan maskin utan NVIDIA bör du inte kopiera den delen rakt av. + +## Begrepp som kan vara bra att känna till + +`sops-nix` och `agenix` är två vanliga sätt att hantera hemligheter i NixOS, +utan att lägga lösenord, tokens eller nycklar i klartext i förrådet. + +- `sops-nix` bygger på Mozilla `sops` +- hemligheter krypteras i filer som kan committas till git +- dekryptering sker på rätt maskin vid aktivering, typiskt med `age` eller GPG +- bra när man vill ha ett centralt, versionshanterat sätt att hantera secrets + +- `agenix` bygger i stället direkt på `age` +- det är ofta enklare och mer minimalt än `sops-nix` +- vanligt val om man bara vill ha några hemligheter och ett lättare upplägg + +Typiska användningsfall är: + +- Wi-Fi-hemligheter +- API-nycklar +- tokens +- privata SSH-nycklar för tjänster +- lösenord till systemtjänster + +`impermanence` är ett upplägg där stora delar av systemet behandlas som +förbrukningsbara mellan boots. + +Det brukar betyda att: + +- root-filsystemet återställs till ett rent grundläge vid boot +- bara vissa kataloger sparas permanent, till exempel `/home`, `/var/lib` eller annat under `/persist` +- systemet blir lättare att hålla rent och reproducibelt + +Det passar extra bra ihop med `ZFS`, eftersom snapshots och rollback redan finns +naturligt där. + +Fördelar med `impermanence`: + +- mindre konfigurationsdrift över tid +- lättare att veta vad som faktiskt är deklarativt +- enklare återställning efter experiment eller fel + +Nackdelen är att det kräver att man tänker igenom vad som faktiskt ska sparas. +Det är ofta något man lägger till senare, när grundinstallationen redan känns stabil. diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c566254 --- /dev/null +++ b/flake.nix @@ -0,0 +1,25 @@ +{ + description = "NixOS configuration"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + disko = { + url = "github:nix-community/disko"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + inputs@{ nixpkgs, disko, ... }: + { + nixosConfigurations.kronos = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = { inherit inputs; }; + modules = [ + disko.nixosModules.disko + ./hosts/kronos + ]; + }; + }; +} diff --git a/hosts/kronos/default.nix b/hosts/kronos/default.nix new file mode 100644 index 0000000..913e2b0 --- /dev/null +++ b/hosts/kronos/default.nix @@ -0,0 +1,66 @@ +{ + config, + pkgs, + ... +}: +let + userName = "love"; + fullName = "Love"; + hostName = "Kronos"; + homeDir = "/home/${userName}"; + installDisk = "/dev/disk/by-id/REPLACE_ME"; +in +{ + _module.args = { + inherit + fullName + homeDir + hostName + installDisk + userName + ; + }; + + imports = [ + ./hardware-configuration.nix + ./disko.nix + ../../modules/base.nix + ../../modules/zfs-root.nix + ../../modules/desktop-hyprland.nix + ]; + + networking.hostName = hostName; + networking.hostId = "ff0b8826"; + + services.xserver.videoDrivers = [ "nvidia" ]; + hardware.nvidia = { + modesetting.enable = true; + powerManagement.enable = false; + powerManagement.finegrained = false; + open = true; + nvidiaSettings = true; + package = config.boot.kernelPackages.nvidiaPackages.stable; + }; + + users.mutableUsers = true; + users.users.${userName} = { + isNormalUser = true; + description = fullName; + extraGroups = [ + "audio" + "input" + "render" + "video" + "wheel" + ]; + shell = pkgs.zsh; + }; + + zramSwap = { + enable = true; + memoryPercent = 75; + algorithm = "zstd"; + }; + + system.stateVersion = "25.11"; +} diff --git a/hosts/kronos/disko.nix b/hosts/kronos/disko.nix new file mode 100644 index 0000000..ac931ca --- /dev/null +++ b/hosts/kronos/disko.nix @@ -0,0 +1,118 @@ +{ installDisk, ... }: +{ + disko.devices = { + disk.main = { + type = "disk"; + device = installDisk; + content = { + type = "gpt"; + partitions = { + ESP = { + label = "EFI"; + size = "1G"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "umask=0077" ]; + }; + }; + + luks = { + label = "cryptroot"; + size = "100%"; + content = { + type = "luks"; + name = "cryptroot"; + settings = { + allowDiscards = true; + }; + content = { + type = "zfs"; + pool = "rpool"; + }; + }; + }; + + }; + }; + }; + + zpool.rpool = { + type = "zpool"; + + options = { + ashift = "12"; + autotrim = "on"; + }; + + rootFsOptions = { + acltype = "posixacl"; + atime = "off"; + compression = "zstd"; + dnodesize = "auto"; + mountpoint = "none"; + normalization = "formD"; + xattr = "sa"; + }; + + datasets = { + root = { + type = "zfs_fs"; + options.mountpoint = "none"; + }; + + "root/nixos" = { + type = "zfs_fs"; + mountpoint = "/"; + options = { + canmount = "noauto"; + mountpoint = "legacy"; + }; + postCreateHook = "zfs snapshot rpool/root/nixos@blank"; + }; + + home = { + type = "zfs_fs"; + mountpoint = "/home"; + options.mountpoint = "legacy"; + }; + + nix = { + type = "zfs_fs"; + mountpoint = "/nix"; + options.mountpoint = "legacy"; + }; + + persist = { + type = "zfs_fs"; + mountpoint = "/persist"; + options.mountpoint = "legacy"; + }; + + var = { + type = "zfs_fs"; + options.mountpoint = "none"; + }; + + "var/log" = { + type = "zfs_fs"; + mountpoint = "/var/log"; + options.mountpoint = "legacy"; + }; + + "var/lib" = { + type = "zfs_fs"; + mountpoint = "/var/lib"; + options.mountpoint = "legacy"; + }; + + reserved = { + type = "zfs_volume"; + size = "8G"; + }; + }; + }; + }; +} diff --git a/hosts/kronos/hardware-configuration.nix b/hosts/kronos/hardware-configuration.nix new file mode 100644 index 0000000..52c14b8 --- /dev/null +++ b/hosts/kronos/hardware-configuration.nix @@ -0,0 +1,3 @@ +{ ... }: +{ +} diff --git a/modules/base.nix b/modules/base.nix new file mode 100644 index 0000000..1f6e24d --- /dev/null +++ b/modules/base.nix @@ -0,0 +1,103 @@ +{ + pkgs, + userName, + ... +}: +{ + nixpkgs.config.allowUnfree = true; + + nix = { + settings = { + auto-optimise-store = true; + experimental-features = [ + "nix-command" + "flakes" + ]; + trusted-users = [ + "root" + "@wheel" + ]; + }; + + gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 14d"; + }; + }; + + boot.loader.systemd-boot.enable = false; + boot.loader.efi.canTouchEfiVariables = true; + boot.loader.grub = { + enable = true; + configurationLimit = 10; + device = "nodev"; + efiSupport = true; + enableCryptodisk = true; + useOSProber = true; + zfsSupport = true; + }; + + time.timeZone = "Europe/Stockholm"; + i18n.defaultLocale = "sv_SE.UTF-8"; + + console = { + font = "Lat2-Terminus16"; + keyMap = "sv-latin1"; + }; + + networking.useNetworkd = true; + systemd.network.enable = true; + systemd.network.wait-online.enable = false; + services.resolved.enable = true; + services.openssh.enable = true; + + systemd.network.networks."10-wired" = { + matchConfig.Name = [ + "en*" + "eth*" + ]; + networkConfig = { + DHCP = "yes"; + IPv6AcceptRA = true; + }; + dhcpV4Config.RouteMetric = 100; + dhcpV6Config.RouteMetric = 100; + }; + + programs.zsh.enable = true; + programs.htop.enable = true; + programs.tmux.enable = true; + programs.git.enable = true; + programs.lazygit.enable = true; + programs.bat.enable = true; + programs.nix-ld.enable = true; + + users.defaultUserShell = pkgs.zsh; + + environment.sessionVariables = { + EDITOR = "nvim"; + NIXOS_OZONE_WL = "1"; + }; + + environment.systemPackages = with pkgs; [ + cowsay + fortune + lolcat + curl + eza + fzf + neovim + odt2txt + openssl + stow + tpm2-tools + unzip + wget + zsh + ]; + + security.sudo.wheelNeedsPassword = true; + + users.users.${userName}.openssh.authorizedKeys.keys = [ ]; +} diff --git a/modules/desktop-hyprland.nix b/modules/desktop-hyprland.nix new file mode 100644 index 0000000..6c650c9 --- /dev/null +++ b/modules/desktop-hyprland.nix @@ -0,0 +1,113 @@ +{ pkgs, ... }: +{ + programs.dconf.enable = true; + programs.hyprland.enable = true; + programs.thunar.enable = true; + programs.thunderbird.enable = true; + programs.xwayland.enable = true; + + hardware.graphics.enable = true; + + security.polkit.enable = true; + security.rtkit.enable = true; + + services.displayManager.defaultSession = "hyprland"; + services.displayManager.sddm = { + enable = true; + wayland.enable = true; + }; + + security.pam.services.login.enableGnomeKeyring = true; + security.pam.services.sddm.enableGnomeKeyring = true; + + services.gnome.gnome-keyring.enable = true; + services.gvfs.enable = true; + services.libinput.enable = true; + services.pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + wireplumber.enable = true; + }; + services.tumbler.enable = true; + services.udisks2.enable = true; + + xdg.portal = { + enable = true; + xdgOpenUsePortal = true; + extraPortals = [ + pkgs.xdg-desktop-portal-gtk + pkgs.xdg-desktop-portal-hyprland + ]; + }; + + fonts.packages = with pkgs; [ + jetbrains-mono + noto-fonts + noto-fonts-cjk-sans + noto-fonts-color-emoji + ]; + + fonts.fontconfig.defaultFonts = { + monospace = [ "JetBrains Mono" ]; + sansSerif = [ "Noto Sans" ]; + serif = [ "Noto Serif" ]; + emoji = [ "Noto Color Emoji" ]; + }; + + environment.systemPackages = with pkgs; [ + brightnessctl + catfish + cliphist + dunst + ffmpegthumbnailer + firefox + flatpak + ghostty + gcr + glib + grim + hypridle + hyprlock + hyprpaper + jq + kdePackages.breeze + libgsf + libnotify + libsecret + libsForQt5.qt5ct + libsForQt5.qtstyleplugin-kvantum + mpv + pavucontrol + papirus-icon-theme + playerctl + python3 + qt6Packages.qt6ct + qt6Packages.qtstyleplugin-kvantum + rofi + slurp + socat + telegram-desktop + waybar + wezterm + wl-clipboard + ristretto + thunar-archive-plugin + thunar-volman + zathura + ]; + + systemd.user.services.hyprpolkitagent = { + description = "Hyprland polkit agent"; + wantedBy = [ "graphical-session.target" ]; + after = [ "graphical-session.target" ]; + partOf = [ "graphical-session.target" ]; + serviceConfig = { + ExecStart = "${pkgs.hyprpolkitagent}/bin/hyprpolkitagent"; + Restart = "on-failure"; + RestartSec = 1; + }; + }; + +} diff --git a/modules/zfs-root.nix b/modules/zfs-root.nix new file mode 100644 index 0000000..3ac9349 --- /dev/null +++ b/modules/zfs-root.nix @@ -0,0 +1,28 @@ +{ ... }: +{ + boot.supportedFilesystems = [ "zfs" ]; + boot.zfs.devNodes = "/dev/disk/by-id"; + boot.initrd.systemd.enable = true; + boot.initrd.luks.devices.cryptroot = { + device = "/dev/disk/by-partlabel/cryptroot"; + allowDiscards = true; + crypttabExtraOpts = [ + "tpm2-device=auto" + "tpm2-pcrs=7" + ]; + }; + + security.tpm2 = { + enable = true; + pkcs11.enable = true; + tctiEnvironment.enable = true; + }; + + services.zfs = { + autoScrub.enable = true; + trim.enable = true; + autoSnapshot.enable = true; + }; + + services.fstrim.enable = false; +}