Linux
There are a lot of Linux distributions to choose from. It can be important to keep in mind what you want to use it for. I have used many distributions. While I like Arch Linux, it requires time to set up. NixOS resolves that issue. Although for beginners, I would recommend something like OpenSUSE or EndeavourOS. OpenSUSE is very solid; I would recommend it for advanced users too. It is easy to install and set up.
NixOS
NixOS focusses on creating a reproducible system. It allows you to get the exact same system running elsewhere easily. Using NixOS is overkill for most people. The time investment might not always be worth it for casual users. If you often find yourself messing with computers or reinstalling then it has benefits.
A great feature is rollbacks, which is useful when things go wrong. It is ideal for Linux enthusiasts who want stable, repeatable configurations. However, the learning curve can be steep especially without guides. For most users, default system settings work fine. It is often better to focus on using the computer rather than constantly tweaking it.
Common Linux mistakes
Linux is often seen as something for computer enthusiasts. But it can be easy to use depending on which operating system you select and which needs you have. Many computer geeks like to tweak, and that can lead to issues (or improvements). For the casual user Windows is fine.
Some common mistakes:
- Configuring zswap without a swap file.
- Using irqbalance; Modern kernels have this behavior already. Although irqbalance can still be useful on servers.
- Modifying kernel variables without understanding what they do. Custom kernels often already set certain variables.
- Accidentally making the system unbootable by not having rollbacks, backups, or a recovery USB.
- ...
A simple configuration
This simple NixOS configuration does not use flakes. It is often beneficial to understand the basic configuration first. Flakes do have benefits, and this article covers them, but not too deeply. The benefits do not always outweigh the cons. The basics of flakes are sufficient for my purposes.
For this example, I merged the hardware-configuration.nix
file into my main configuration for simplicity, but it's usually cleaner to keep them separate. Note that Nix file syntax can change, so if the configuration doesn't work for you, feel free to reach out (excluding the hardware configuration).
This configuration has some issues, but the essentials function:
- KWallet autounlock works only with an empty password.
- KDE automount for drives does not function.
- Drawing tablets may have issues under Wayland; however, my setup uses Wayland, and Krita runs smoothly. It will only improve.
Finding options
You can find options and packages on these websites:
- NixOS Options
- Home Manager Options (home manager is used later in this article)
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running ‘nixos-help’).
{ config, lib, pkgs, modulesPath, ... }:
{
# START HARDWARE
# Lenovo Legion T5-26AMR5 (but with added RAM)
boot = {
initrd = {
availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" ];
};
kernelPackages = pkgs.linuxPackages_zen;
kernelModules = [ "kvm-amd" ];
# Blacklist nouveau to make sure we enforce the closed source nvidia driver
# Ethernet can act oddly sometimes I blacklist it
blacklistedKernelModules = [ "nouveau" ]; # "r8169" ];
# mitigations=off dangerous - more performance, less security - not relevant for modern CPUs
# Kernel parameters
# Add mitigations=off if more performance, less security is wanted, not relevant for modern CPUs etc
kernelParams = [
# Note: zswap is enabled by default with the zen kernel
# https://github.com/zen-kernel/zen-kernel/wiki/Detailed-Feature-List#ram
"zswap.enabled=1" # Enable zwap, other zswap settings are inherited of zen kernel
"quiet" # Clean boot screen, suppress most kernel messages
"rd.systemd.show_status=auto" # Only show errors of systemd, ignore other output during boot
"rd.udev.log_priority=3" # Only show errors of udev ignore other output during boot
"amd_pstate=guide" # Amd stuff is for amd cpu
"nowatchdog" # Normal users do not need watchdogs
"nmi_watchdog=0" # See previous watchdog comment
];
kernel.sysctl = {
# DEFAULTS ARE FINE IF YOU DO NOT KNOW WHAT YOU ARE DOING
# CHANGING IT CAN DO MORE WRONG THAN RIGHT - LEAVING SYSCTL ALONE IS FINE
# Check the following links if you want to tweak this
# https://github.com/tolgaerok/nixos-kde/blob/main/core/modules/system-tweaks/kernel-tweaks/64GB-SYSTEM/64GB-SYSTEM.nix
# https://github.com/tolgaerok/nixos-kde/blob/main/core/modules/system-tweaks/storage-tweaks/SSD/SSD-tweak.nix
# Nobara Tweaks
"fs.aio-max-nr" = 1000000;
"fs.inotify.max_user_watches" = 65536;
"kernel.panic" = 5;
"kernel.pid_max" = 131072;
# SSD tweaks: Adjust settings for an SSD to optimize performance.
"vm.dirty_background_ratio" = "40";
"vm.dirty_expire_centisecs" = "3000";
"vm.dirty_ratio" = "80";
"vm.dirty_time" = "0";
"vm.dirty_writeback_centisecs" = "300";
};
# Use tmpfs for /tmp
tmp = {
cleanOnBoot = true;
useTmpfs = true;
};
};
# Enable trim on SSD
# Trim service can better than using discard as mount option (never use both)
services.fstrim.enable = true;
# Basic filesystems (ext4, it has much history and is well-tested)
fileSystems."/" = {
# Mount disk by label
device = lib.mkForce "/dev/disk/by-label/NixOS";
fsType = "ext4";
# no access times to reduce writes but access time can be useful metadata,
# use relatime if you are not sure noatime includes nodiratime
# commit=600 - if you lose your power, you will lose as much as the latest 600 seconds of work, but it does cause less writes
options = [ "rw" "noatime" "commit=600" ];
};
fileSystems."/boot" = {
device = lib.mkForce "/dev/disk/by-label/BOOT";
fsType = "vfat";
};
# Put run in tmpfs
fileSystems."/run" = {
device = "tmpfs";
# The size is a limit on the size, and is not kept reserved explicity for tmpfs.
options = [ "size=3G" ];
};
# Zswap requires a swapfile or partition to work correctly
swapDevices = [{
device = "/var/lib/swapfile";
# Swap is used when your RAM is full. It shouldn't happen often,
# but you will be thankful that you have it when it is needed.
# RAM size (8 GB) + 2 GB
size = (1024 * 8) + (1024 * 2);
}];
# Allow closed-source firmware
hardware.enableRedistributableFirmware = true;
# Enables DHCP on each ethernet and wireless interface. This is the recommended approach.
networking.useDHCP = lib.mkForce true;
nixpkgs.hostPlatform = "x86_64-linux";
# AMD microcode
hardware.cpu.amd.updateMicrocode = true;
# My desktop has TPM but I don't seem to need it
# security.tpm2.enable = true;
# Bluetooth
hardware.bluetooth.enable = true;
hardware.bluetooth.powerOnBoot = false; # disable bluetooth on boot
# Note to self, vulkan is on the GPU, do not add amd vulkan stuff
# (I knew this, but somehow made that mistake)
hardware.graphics = {
enable = true; # enable opengl
extraPackages = [
pkgs.vaapiVdpau
pkgs.nvidia-vaapi-driver
];
enable32Bit = true;
};
# Load nvidia driver (for Xorg or Wayland, config is the same)
services.xserver.videoDrivers = [ "nvidia" ];
hardware.nvidia = {
# Modesetting is required.
modesetting.enable = true;
# Nvidia power management. Experimental, and can cause sleep/suspend to fail.
# Enable this if you have graphical corruption issues or application crashes after waking
# up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead
# of just the bare essentials.
powerManagement.enable = false;
# Fine-grained power management. Turns off GPU when not in use.
# Experimental and only works on modern Nvidia GPUs (Turing or newer).
powerManagement.finegrained = false;
# Use the NVidia open source kernel module (not to be confused with the
# independent third-party "nouveau" open source driver).
# Support is limited to the Turing and later architectures. Full list of
# supported GPUs is at:
# https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus
# Only available from driver 515.43.04+
# Currently alpha-quality/buggy, so false is currently the recommended setting.
open = true;
# Enable the Nvidia settings menu,
# accessible via `nvidia-settings`.
nvidiaSettings = true;
# Optionally, you may need to select the appropriate driver version for your specific GPU.
package = config.boot.kernelPackages.nvidiaPackages.stable;
};
# END HARDWARE
# START CONFIG
# Bootloader
boot = {
loader = {
systemd-boot = {
enable = true;
configurationLimit = 5;
};
efi.canTouchEfiVariables = true;
};
# NTFS is useful if you want to mount windows drives
supportedFilesystems = [ "ntfs" ];
};
networking = {
hostName = "nixos";
networkmanager.enable = true;
# Cloudflare dns, I am not a big fan of them, but they are the fastest
nameservers = [ "1.1.1.1" "1.0.0.1" ];
stevenblack = {
# Block ads and other things by using the hosts file
enable = true;
block = [ "fakenews" "gambling" "porn" ];
};
};
systemd.services = {
# Fwupdmgr is not in the path - use the full path
firmware-update = {
description = "Update firmware.";
script = ''
/run/current-system/sw/bin/fwupdmgr refresh --offline --assume-yes || true > /dev/null
/run/current-system/sw/bin/fwupdmgr update --offline --assume-yes > /dev/null
'';
serviceConfig.Type = "oneshot"; # Run the script once
wantedBy = [ "multi-user.target" ]; # Start after login
};
};
# Set your time zone.
time.timeZone = "Europe/Brussels";
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
# KDE
services.displayManager = {
autoLogin = {
enable = true;
user = "rmw";
};
# I am using wayland now
# defaultSession = "plasmax11";
};
environment.variables = {
# nvidia force closed source driver
GBM_BACKEND = "nvidia-drm";
LIBVA_DRIVER_NAME = "nvidia";
__GLX_VENDOR_LIBRARY_NAME = "nvidia";
# this envs are useful for electron wayland
ELECTRON_OZONE_PLATFORM_HINT = "auto";
# variable for qt (wayland with fallback to x11)
QT_QPA_PLATFORM = "wayland;xcb";
# set sessiontype
XDG_SESSION_TYPE = "wayland";
};
services.desktopManager.plasma6.enable = true;
# pam_wallet will attempt to unlock the user's default KDE wallet upon login.
# If the user has no kdewallet, or the login password does not match their wallet password,
# KDE will prompt separately after login. sddm is the displaymanager
# kwallet-pam is not compatible with GnuPG keys, the KDE Wallet must use the standard blowfish encryption
# The wallet must be named kdewallet (default name). It does not unlock any other wallet(s).
# It may be needed to remove the default created wallet first, thus removing all stored entries.
# If the kwallet Migration Assistant asks for a password after every login, rename or delete the ~/.kde4/share/apps/kwallet folder.
# If nothing works you can set it to an empty password, I ended up doing this, as I use keepassxc for most passwords
pam.services = {
sddm.kwallet.enable = true;
# It may not autologin without this because sddm hardcodes PAM to the login rules
login.kwallet.enable = true;
};
environment.plasma6.excludePackages = with pkgs.kdePackages; [
gwenview
okular
oxygen
khelpcenter
plasma-browser-integration
print-manager
baloo # Disables indexing!! Be careful!
];
# Drawing tablet
hardware.opentabletdriver.enable = true;
# Configure keymap in X11
# Disable the X11 windowing system (wayland is in use instead)
services.xserver.enable = false; # true;
libinput = {
# Mouse
enable = true;
mouse.accelProfile = "flat"; # disable mouse acceleration
touchpad.accelProfile = "flat"; # disable touchpad acceleration
};
nix = {
optimise.automatic = true;
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
settings = {
auto-optimise-store = true;
max-jobs = "auto";
substituters = [
"https://nix-community.cachix.org"
"https://cache.nixos.org/"
];
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
};
};
# Don't install documentation I don't use it
documentation.enable = false; # documentation of packages
# Enable ananicy
services.ananicy = {
enable = true;
package = pkgs.ananicy-cpp;
rulesProvider = pkgs.ananicy-cpp;
settings = {
apply_nice = true;
};
};
# update firmware
# list of supported devices: https://fwupd.org/lvfs/devices/
services.fwupd.enable = true;
# Command to update: fwupdmgr refresh && fwupdmgr update
# Earlyoom killer
systemd.oomd.enable = false;
services.earlyoom.enable = true;
# Keep journal in memory, do not store on disk
# To reduce the size and keep it on disk use services.journald.extraConfig
# DANGEROUS if you have boot issues!!!
services.journald.storage = "volatile";
# I do not use coredumps, and disabling storing them is better for security
systemd.coredump.extraConfig = ''
Storage=none
ProcessSizeMax=0
'';
# disable suspend, hibernate and sleep
systemd.sleep.extraConfig = ''
AllowSuspend=no
AllowHibernation=no
AllowHybridSleep=no
AllowSuspendThenHibernate=no
'';
# Make nixos boot slightly faster by turning these off during boot
systemd.services.NetworkManager-wait-online.enable = false;
systemd.services.systemd-udev-settle.enable = false;
# Schedulers from https://wiki.archlinux.org/title/improving_performance
services.udev.extraRules = ''
# HDD
ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"
# SSD
ACTION=="add|change", KERNEL=="sd[a-z]*|mmcblk[0-9]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="bfq"
# NVMe SSD
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="none"
'';
# Enable sound with pipewire.
sound.enable = true;
hardware.pulseaudio.enable = false;
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
# Define a user account. Don't forget to set a password with ‘passwd’.
users.users.rmw = {
initialPassword = "r@@t";
isNormalUser = true;
description = "Robin Wils";
extraGroups = [ "networkmanager" "network" "wheel" "storage" "disk" ];
packages = with pkgs; [
# Make sure that every character can be displayed by adding this font
noto-fonts
kate # simple text editor
krita # drawing program
keepassxc # password database
brave # browser
vscodium # vscode
htop # commandline performance monitor
mpv # commandline video player
bun # similar to npm, but different
archiver # lots of archivers & unarchivers into one package
# Archive syntax: arc archive [archive name] [input files...]
# Extract syntax: arc unarchive [archive name] [destination]
# list syntax: arc ls [archive name]
# nix file formatter
nixfmt-rfc-style
# GAMING (I am not a big gamer, only for party games)
r2modman # modmanager, using it for lethal company
# steam package
obs-studio # recording
shotcut # edit recordings
# Automount - is easier to configure with home manager more on this later
udiskie
# qt recommends this system package for wayland
qt6.qtwayland
];
};
# List packages installed in system profile. To search, run:
# $ nix search wget
environment.systemPackages = with pkgs; [
# Nano editor is installed by default on nixos.
nixfmt-rfc-style # nix file formatter
doas-sudo-shim # Replaces sudo/aliases with doas for compatibility
];
services.emacs.defaultEditor = true;
programs = {
# xwayland is often useful in wayland
xwayland.enable = true;
kdeconnect.enable = true;
steam = {
enable = true;
remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play
dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server
# steam input in wayland - translate X11 input events to uinput events
extest.enable = true;
};
git = {
enable = true;
config = {
user.name = "Robin Wils";
user.email = "mrwils@tutanota.com";
};
};
ssh.startAgent = true;
};
# Allow unfree packages
nixpkgs.config.allowUnfree = true;
security = {
# Doas instead of sudo (better for security). The doas-sudo-shim package is included in the system packages, which replaces sudo/aliases with doas for compatibility
sudo.enable = false;
doas = {
enable = true;
extraRules = [
{
users = [ "rmw" ];
keepEnv = true;
persist = true; # Only require password once
}
];
};
};
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "24.05"; # Did you read the comment?
# END CONFIG
}
To build the system you can execute "sudo nixos-rebuild switch
".
Home manager & flakes
Flakes and home manager enhance reproducibility and simplify managing personal settings. Using them does not have to be hard. The benefits are clear.
Flakes
One reason you may want to use flakes is to make the system more reproducable. Another reason is that many GitHub projects only provide a NixOS package in the flake format, and certain features are only available by using flakes.
Moving to flakes is easy, and does not have to be scary. You just need to make a simple flake.nix
file, and modify nix in the configuration.nix
file.
# /etc/nixos/flake.nix
{
description = "flake for yourHostNameGoesHere";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs =
{ nixpkgs, ... }@inputs:
{
nixosConfigurations.yourHostNameGoesHere = nixpkgs.lib.nixosSystem {
modules = [ ./configuration.nix ];
};
};
}
# Update in /etc/nixos/configuration.nix
nix = {
extraOptions = ''
experimental-features = nix-command flakes
'';
};
Flakes only work if the files are tracked by Git. You do not need to commit anything. Just go to /etc/nixos
and execute "git init && git add .
". When you use flakes you do need to use a new command to build your system:
sudo nixos-rebuild --flake .#yourHostNameGoesHere switch
Channels and inputs
Without flakes, NixOS uses channels to manage updates. With flakes, a similar concept called inputs is used. Both channels and inputs need to be updated periodically, as they point to specific versions of the NixOS repository.
Packages are linked to directories within that repository, which allows them to receive updates even if your flake is not fully up to date. You can run the command nix flake update /etc/nixos
to refresh the inputs. If you notice that a package cannot be found in your repository, it may be a good indication that it is time to use this command. Although this situation is quite rare.
Home manager
Home manager makes even more of your setup reproducible. It is focused on reproducing the dotfiles (configuration for specific programs) in your home. The basic packages do not require home manager, but people highly recommend it.
Enabling it is not difficult, and in my previous example without home manager you may have noticed that udiskie was not configured. Home manager has better options for it. The different options in home manager are a good reason to start using it.
To enable home manager we have to update our flake.nix
and configuration.nix
. We also have to add a home.nix
file. After that you can rebuild the flake.
# /etc/nixos/flake.nix
{
description = "flake for yourHostNameGoesHere";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# Add home manager
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{ nixpkgs, ... }@inputs:
{
nixosConfigurations.yourHostNameGoesHere = nixpkgs.lib.nixosSystem {
modules = [
./configuration.nix
# Add home manager
inputs.home-manager.nixosModules.default
];
};
};
}
# /etc/nixos/configuration.nix
# Add inputs if it wasn't present yet
{ pkgs, lib, inputs, ...}
{
# Add this part
home-manager = {
# extraSpecialArgs are only needed if you import more modules to home manager. This can be useful for modules like impermanence.
extraSpecialArgs = { inherit inputs; };
users.yourusername = import ./home.nix;
};
}
# /etc/nixos/home.nix
{ pkgs, inputs, ... }:
{
# Enable unfree packages (it is enabled in configuration.nix, but needs to be enabled per user too)
nixpkgs.config.allowUnfree = true;
home = {
keyboard.layout = "us";
# Packages for your user (remove these out of your configuration.nix)
packages = with pkgs; [
# -- Cli --
archiver # lots of archivers & unarchivers into one package
# Archive syntax: arc archive [archive name] [input files...]
# Extract syntax: arc unarchive [archive name] [destination]
# list syntax: arc ls [archive name]
gdu # Disk usage analyzer
htop # Commandline taskmanager
lsd # Better ls
fd # Better find
# -- Development --
bun # similar to npm, but different
nodePackages_latest.pnpm # bun does not always work
powershell # Can often be powerful
vscodium # Visual studio code fork without telemetry
# -- Gaming --
prismlauncher # minecraft launcher & modmanager
r2modman # modmanager, using it for lethal company
xorg.libxcb # required for steam according to people, but steam works without it
# -- Photography --
ansel # If ansel does not work, I use darktable. This package sometimes breaks
# -- Basic tools --
noto-fonts # Every character can be displayed by adding this font
keepassxc # Password manager (highly recommended)
mpv # Video player
krita # Drawing/painting application
obs-studio # Screen recorder & live-streaming
shotcut # Video editting
feh # Image viewer (command-line)
imagemagick # Image convert commands
ffmpeg # Video convert commands
# -- Wayland --
# qt recommends this system package for wayland
qt6.qtwayland
];
};
programs = {
home-manager.enable = true;
# -- Cli --
bat.enable = true; # Better cat
zoxide.enable = true; # Better cd (use z "your-path")
bash = {
enable = true;
historySize = 5000;
historyFileSize = 5000;
historyIgnore = [ "htop" ];
historyControl = [ "erasedups" ];
shellOptions = [ "cmdhist" ];
shellAliases = {
cat = "bat";
find = "fd";
ls = "lsd -Sl";
lsa = "lsd -Sla";
less = "less -R";
};
bashrcExtra = ''
# -- functions --
function nixos-update-channels {
nix flake update /etc/nixos
}
function nixos-upgrade {
nixos-rebuild switch --use-remote-sudo --flake /etc/nixos#
}
function nixos-upgrade-no-switch {
nixos-rebuild boot --use-remote-sudo --flake /etc/nixos#
}
function format-nix-config {
find /etc/nixos/ -name '*.nix' -exec nixfmt {} \;
}
'';
};
git = {
enable = true;
userEmail = "mrwils@tutanota.com";
userName = "Robin Wils";
};
};
# Here is the part in which udiskie is enabled
services = {
kdeconnect.enable = true; # remote connect
ssh-agent.enable = true; # Ssh agent (useful for git)
udiskie.enable = true; # Automount - make sure your user is in the disk group
};
# https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
home.stateVersion = "24.05";
}
Going more NixOS than NixOS
As mentioned NixOS is all about being reproducible. There is one thing missing though. Cached files and other cluttered data may remain on your disk every boot. Programs may set their own configurations without you knowing it. You could miss certain files when you make a backup. But this issue can be resolved!
While impermanence is an advanced topic, it is still worth considering. It does not have to be that difficult, but I do recommend making a backup of your system first. Impermanence is a tool you can use to wipe your system except for your configured directories every boot. It does this by putting your root system in RAM, and mounting directories which you save permanently. You do not need tons of RAM to use this, but additional RAM does help.
The benefits are that you keep a clean system and that making backups becomes incredibly easy. Maintaining impermanence and figuring out what to keep is a bit difficult, but they are working on fixing that.
The GitHub page of impermanence is clear. I suggest reading through it. We can migrate to impermanence from our previous configuration without losing data.
Impermanence
We have to update the flake again.
# /etc/nixos/flake.nix
{
description = "flake for yourHostNameGoesHere";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
# Add impermanence
impermanence = {
url = "github:nix-community/impermanence";
};
};
outputs =
{ nixpkgs, ... }@inputs:
{
nixosConfigurations.yourHostNameGoesHere = nixpkgs.lib.nixosSystem {
modules = [
./configuration.nix
inputs.home-manager.nixosModules.default
# Add impermanence
inputs.impermanence.nixosModules.impermanence
];
};
};
}
# Update your filesystems (in hardware-configuration.nix or configuration.nix). Using btrfs is also an option, see the github of impermanence.
# Remove use tmpfs for /tmp, as it will be on tmpfs anyway
# boot = {
# Use tmpfs for /tmp
# tmp = {
# cleanOnBoot = true;
# useTmpfs = true;
# };
# };
# System on tmpfs (Impermanence)
fileSystems."/" = {
fsType = "tmpfs";
# The size is a limit on the size, and is not kept reserved explicity for tmpfs. I have 80G of RAM, 7% is 5.6G which is more than enough, 2G is probably enough already
options = [
"size=7%"
"mode=755"
];
};
fileSystems."/boot" = {
device = "/dev/disk/by-label/BOOT";
fsType = "vfat";
};
fileSystems."/persist" = {
device = "/dev/disk/by-label/NixOS";
fsType = "ext4";
neededForBoot = true; # Persist is required to boot!!
options = [
"rw"
"noatime"
"commit=600"
];
};
# Mount the nix folder of our persist (our only drive) to /nix instead of /persist/nix https://nixos.wiki/wiki/Filesystems
fileSystems."/nix" = {
device = "/persist/nix";
options = [ "bind" "noatime" ];
};
# Zswap requires a swapfile or partition to work correctly
swapDevices = [
{
device = "/persist/system/var/lib/swapfile";
# Swap is used when your RAM is full. It shouldn't happen often, but you will be thankful that you have it when it is needed.
# RAM size 8 GB + 2 GB (since I have enough storage space) - I now have more than 8GB RAM, but decided to not change swap
size = (1024 * 8) + (1024 * 2);
}
];
I decided to separate the configuration of the root files to persist in a different file for clarity. Although the configuration of files to persist for home is in the home.nix
.
# /etc/nixos/configuration.nix
imports = [
# If you have your hardware config in a separated file leave this line
./hardware-configuration.nix
# Add the impermanence config
./impermanence-root.nix
];
# /etc/nixos/impermanence-root.nix
{ pkgs, lib, ... }:
{
environment = {
# Persist these files, all other files will be removed in home.nix there are additional files to persist configured
persistence."/persist/system" = {
hideMounts = true;
directories = [
# Cannot be created by impermanence (they have to exist on disk)
{
directory = "/var/lib/nixos";
user = "root";
group = "root";
mode = "0755";
}
{
directory = "/var/log";
user = "root";
group = "root";
mode = "0755";
}
# Everything should be cleaned up by systemd-tmpfiles over time anyway
{
directory = "/var/lib/systemd";
user = "root";
group = "root";
mode = "0755";
}
{
directory = "/var/tmp";
user = "root";
group = "root";
mode = "1777";
}
{
directory = "/var/spool";
user = "root";
group = "root";
mode = "0777";
}
# nix tmp dir for rebuilds, don't fill our tmpfs root with that
{
directory = "/var/cache/nix";
user = "root";
group = "root";
mode = "u=rwx,g=rx,o=rx";
}
# NetworkManager
{
directory = "/etc/NetworkManager/system-connections";
mode = "0700";
}
{
directory = "/var/lib/NetworkManager";
mode = "0755";
}
# Printing
# { directory = "/var/lib/cups"; user = "root"; group = "root"; mode = "0755"; }
# { directory = "/var/cache/cups"; user = "root"; group = "lp"; mode = "0770"; }
"/var/lib/bluetooth" # bluetooth state
"/etc/nixos" # Configs
"/etc/rustic" # Backup script
# "/var/lib/btrfs" # records fs scrubbing status: https://btrfs.readthedocs.io/en/latest/Scrub.html (although I currently do not use scrub)
# persist fwupd files to cache metadata and schedule updates for boot
"/var/lib/fwupd"
"/var/cache/fwupd"
];
files = [
"/etc/adjtime" # Is meant to stay persistent on Linux
"/etc/machine-id" # Is meant to stay persistent on Linux
];
};
};
programs.fuse.userAllowOther = true; # Fuse is required for impermanence
# persist passwords
# https://github.com/nix-community/impermanence/issues/120
# system.activationScripts = {
# etc_shadow = ''
# [ -f "/etc/shadow" ] && cp /etc/shadow /persist/system/etc/shadow
# [ -f "/persist/system/etc/shadow" ] && cp /persist/system/etc/shadow /etc/shadow
# '';
# users.deps = ["etc_shadow"];
# };
# systemd.services."etc_shadow_persistence" = {
# enable = true;
# description = "Persist /etc/shadow on shutdown.";
# wantedBy = ["multi-user.target"];
# path = [pkgs.util-linux];
# unitConfig.defaultDependencies = true;
# serviceConfig = {
# Type = "oneshot";
# RemainAfterExit = true;
# # Service is stopped before shutdown
# ExecStop = pkgs.writeShellScript "persist_etc_shadow" ''
# mkdir --parents "/persist/system/etc"
# cp /etc/shadow "/persist/system/etc/shadow"
# '';
# };
}
# /etc/nixos/home.nix
{ pkgs, inputs, ... }:
{
# Import impermanence
imports = [ inputs.impermanence.nixosModules.home-manager.impermanence ];
# Add to your home section (packages etc can remain there)
home = {
# IMPORTANT: Set files to keep every reboot (persistence)
# KDE has tons of files https://nixos.wiki/wiki/Impermanence
persistence."/persist/home/rmw" = {
directories = [
"Downloads"
"Music"
"Pictures"
"Documents"
"Videos"
"Desktop"
# cache
".cache/nix" # avoid unnecessary fetching
".cache/nvidia" # avoid unnecessary computation
# KDE & KDE connect
".config/KDE"
".config/kde.org"
".config/kdedefaults"
".config/kdeconnect"
".local/share/kwalletd" # after a wallet with empty password is made, also save it
# Brave
".config/BraveSoftware/Brave-Browser/Default/Local Storage"
".config/BraveSoftware/Brave-Browser/Default/Extensions"
".config/BraveSoftware/Brave-Browser/Default/BraveWallet"
".config/BraveSoftware/Brave-Browser/Default/Sessions"
# Keepass
".cache/keepassxc"
".config/keepassxc"
# r2modman - Likely can be reduced in what is kept
".config/r2modman"
".config/r2modmanPlus-local"
# vesktop
".config/vesktop/settings"
".config/vesktop/sessionData"
# VSCodium
".config/VSCodium/CachedProfilesData"
".config/VSCodium/CachedExtensionVSIXs"
".config/VSCodium/User"
".config/.vscode-oss"
# emacs
".emacs.d"
# steam
".local/share/Steam"
# Pnpm
".local/share/pnpm"
# Bun
".bun"
# OBS
".config/obs-studio"
# Zoxide cache
".local/share/zoxide/"
# keys
".gnupg"
".local/share/keyrings"
".ssh"
];
files = [
".bash_history"
# KDE
".config/kdeglobals"
".config/klanguageoverridesrc"
".config/kscreenlockerrc"
".config/ksplashrc"
".config/ktimezonedrc"
".config/kwalletrc"
".config/mimeapps.list"
".config/plasma-localerc"
".config/plasmarc" # wallpaper
".config/kwinrc"
".config/baloofilerc" # indexing
".config/plasma-org.kde.plasma.desktop-appletsrc" # wallpaper
# Codium
".config/VSCodium/User/settings.json"
# Vesktop
".config/vesktop/settings.json"
".config/vesktop/state.json"
# Keepass
".config/KeePassXCrc"
".cache/keepassxc/keepassxc.ini"
# Krita
".config/kritadisplayrc"
".config/kritarc"
".local/share/krita"
# Brave
".config/BraveSoftware/Brave-Browser/Default/Bookmarks"
".config/BraveSoftware/Brave-Browser/Default/Favicons"
".config/BraveSoftware/Brave-Browser/Default/Cookies"
".config/BraveSoftware/Brave-Browser/Default/Preferences"
".config/BraveSoftware/Brave-Browser/Default/Secure Preferences"
".config/BraveSoftware/Brave-Browser/Default/Local State"
".config/BraveSoftware/Brave-Browser/Default/Web Data"
".config/BraveSoftware/Brave-Browser/Default/Login Data"
".config/BraveSoftware/Brave-Browser/Default/Login Data For Account"
# OpenTabletDriver
".config/OpenTabletDriver/settings.json"
# Freetube
".config/FreeTube/settings.db"
".config/FreeTube/profiles.db"
];
allowOther = true;
};
};
}
Before you rebuild
I highly recommend making a backup first.
You may want to add the following function to your bashrcExtra
to check which files are in tmpfs. Those files won't be persisted. It can be difficult to figure out which files to persist but impermanence is working on a solution for that
function get-files-in-tmpfs {
sudo fd --one-file-system --base-directory / --type f --hidden --exclude "{tmp,etc/passwd}";
}
Rebuilding won't work yet. Before you rebuild you require a system directory on your root. Within impermanence-root.nix
you can see which files impermanence expects. Make sure they exists before you rebuild. If you have issues with booting you can still rollback. There is no danger in trying.
I made a script to make the migration easier. It copies the directories you need to system. Update the variables to your situation before running it. It also sets permissions which likely isn't needed, if you applied that section make sure to change the hardcoded user.
#!/bin/bash
# Function to copy directories and files
copy_items() {
local SOURCE_DIR_PREFIX="$1"
local TARGET_DIR_PREFIX="$2"
local DIRS=("${!3}")
local FILES=("${!4}")
# Copy directories to each target directory
for DIR in "${DIRS[@]}"; do
# Construct the corresponding source directory path
FULL_SOURCE_DIR="$SOURCE_DIR_PREFIX/$DIR"
# Construct the full target directory path
FULL_TARGET_DIR="$TARGET_DIR_PREFIX/$DIR"
# Ensure the target directory exists
mkdir -p "$FULL_TARGET_DIR"
# Copy files from the source to the target directory
if [ -d "$FULL_SOURCE_DIR" ]; then
cp -rp "$FULL_SOURCE_DIR/"* "$FULL_TARGET_DIR/" || {
echo "Failed to copy from $FULL_SOURCE_DIR to $FULL_TARGET_DIR"
}
fi
done
# Copy specific files to the target directory
for FILE in "${FILES[@]}"; do
# Construct the full source file path
FULL_SOURCE_FILE="$SOURCE_DIR_PREFIX/$FILE"
# Construct the target file path
FULL_TARGET_FILE="$TARGET_DIR_PREFIX/$FILE"
# Ensure the target directory exists
mkdir -p "$(dirname "$FULL_TARGET_FILE")"
# Copy the file if it exists
if [ -f "$FULL_SOURCE_FILE" ]; then
cp -p "$FULL_SOURCE_FILE" "$FULL_TARGET_FILE" || {
echo "Failed to copy $FULL_SOURCE_FILE to $FULL_TARGET_FILE"
}
fi
done
}
## /system
# Source directory for system
SOURCE_DIR_PREFIX="/"
TARGET_DIR_PREFIX="/system"
DIRS=(
"var/lib/nixos"
"var/log"
"var/lib/systemd"
"var/tmp"
"var/spool"
"/var/cache/nix"
"/etc/NetworkManager/system-connections"
"var/lib/NetworkManager"
"var/lib/bluetooth" # bluetooth state
"/etc/nixos"
"/etc/rustic"
"var/lib/fwupd"
"var/cache/fwupd"
)
FILES=(
"/etc/adjtime" # Is meant to stay persistent on Linux
"/etc/machine-id" # Is meant to stay persistent on Linux
"/var/lib/swapfile" # Swap file
)
# Call the function for system
copy_items "$SOURCE_DIR_PREFIX" "$TARGET_DIR_PREFIX" DIRS[@] FILES[@]
# Set the correct owner
chown -R rmw /home
chown -R root /system/var
chown -R root /system/etc
# Set permissions for /var/log
chmod 750 /system/var/log
chmod 755 /system/var/lib
chmod 1777 /system/var/tmp
chmod 755 /system/etc
If you decided to use my aliases for nixos-upgrade
be careful. We are changing filesystems here, so we need to call nixos-upgrade-no-switch
instead. In fact, you always need to call that one on impermanence. Switching immediately does not seem to work on it. You need to reboot manually after updating the system.
# Use boot instead of switch!
nixos-rebuild boot --use-remote-sudo --flake /etc/nixos#
After your build
There are some unnecessary files left over of our previous config. We only required /system
, /boot
, /nix
and /home
now. So you can remove all other directories on the root.
# Remove all directories on root except the required ones
sudo find / -mindepth 1 -maxdepth 1 -type d ! -name 'system' ! -name 'boot' ! -name 'nix' ! -name 'home' -exec rm -rf {} +
NixOS config
You can find my NixOS configuration on Git. I do use impermanence. But reading other people's configurations can be useful even if you do not use flakes, home-manager or impermanence. It gives you an idea of available options.
GitLab (NixOS-config)