System snapshots
Omarchy takes a Btrfs snapshot with snapper before every update, and you boot into one from the Limine menu to undo a bad update. None of that machinery exists here, and none of it is missing. NixOS keeps every previous system on disk as a generation, and generations do everything the snapshot did.
What generations do not cover is /home and the mutable state under
/var/lib — so on a machine the nixarchy installer built, snapper covers
exactly that half. See snapshots of your home directory
below. On a machine that imported the nixarchy module into a configuration of
its own, there is no snapper at all: nixarchy does not take snapshots of a disk
it did not lay out, and omarchy snapshot says so.
What a generation is
Every nixos-rebuild switch (which is what omarchy update runs) builds a
complete new system in /nix/store and points
/nix/var/nix/profiles/system at it. The old one is not overwritten; it is
still there, complete, and it is still listed in the boot menu.
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system
No snapshot is taken before an update because there is nothing to snapshot: the previous system was never modified in the first place.
How long a generation lasts
Generations are what rollback is made of, so they are not collected eagerly.
On a machine the installer built, nh keeps the last five and anything
from the last fourteen days, whichever is more – long enough to outlast
the gap between noticing a problem and having time to look at it.
Separately, and for a different reason, the Nix daemon collects unreferenced store paths when free space falls below 5 GiB. That is garbage, not generations: a package you tried and closed, a build input nothing needs. Collecting it can never cost you a rollback, because every generation is a garbage-collection root.
The boot menu shows the most recent twenty entries. That is a cap on the
menu, not on the generations – --list-generations above still shows
everything the store holds, and --rollback still reaches the one before
this.
If you added nixarchy to a machine you already run, none of this applies: your own policy stands.
Rolling back
From a running desktop:
sudo nixos-rebuild --rollback switch # back one generation, now
From a machine that will not reach a desktop: reboot, and pick the previous generation from the boot menu. Every generation is an entry there, labelled with its date and number.
This is the one way generations are better than snapper, and it matters. Upstream’s restore is a command you run from inside the booted snapshot, and a broken update that keeps the machine from booting at all leaves you with the Limine menu only if you are not using direct boot. Here the boot menu is where the generations live, so the recovery route works precisely when it is needed most: when the update cost you the screen, the GPU driver, or the login manager.
To see what an update actually changed, before deciding whether to roll it back:
nix profile diff-closures --profile /nix/var/nix/profiles/system
What a rollback does and does not touch
The same boundary upstream describes for snapshots holds here, for the same reason:
| Packages, services, kernel, drivers, the Omarchy tree | Rolled back with the generation |
/home, ~/.config, your app selection file |
Left exactly as they are |
So a rollback fixes a broken system update and does not recover a deleted file. And if an application rewrote its config in a new format before you rolled back, the config stays in the new format; sort that out by hand, as upstream says.
Rolling back also does not edit your flake. flake.lock still names the
inputs that produced the broken generation, so the next omarchy update or
nixos-rebuild switch builds it again. To make the rollback stick, revert the
lock file too, most simply with git checkout flake.lock in your flake if it
is under version control, or by updating only the input that did not break.
Testing a change without committing to it
Snapshots exist to make an update safe to try. Generations make that a first-class option instead:
sudo nixos-rebuild boot --flake <flake> # becomes the default at next boot, running system untouched
sudo nixos-rebuild test --flake <flake> # activates now, boot default untouched
boot is the one for anything that could cost you the screen; the previous
generation stays one menu entry away. See
updating NixOS.
Snapshots of your home directory
The installer lays the disk out with two subvolumes that exist only to hold
snapshots — @snapshots mounted at /.snapshots, and @home-snapshots at
/home/.snapshots — and configures snapper against them: an hourly timeline of
/home, and one snapshot of / at every boot.
omarchy snapshot # what exists
omarchy snapshot create "before I broke it"
omarchy snapshot restore # pick one, and copy files back out of it
restore copies rather than swapping the subvolume. Upstream swaps, which
replaces the whole of /home in one step — including everything written since
the snapshot — and cannot be done while the subvolume is mounted and in use.
Somebody reaching for restore has usually broken one config file, so what this
prints is the path the snapshot is readable at, plus snapper status to see
what changed. Nothing is written on your behalf.
Snapshots live on the same disk as the thing they snapshot. They are an undo button, not a backup: they do not survive the disk failing.
If your machine was installed before those subvolumes existed
They were added in #114.
A machine installed before it has snapper — its next rebuild pulls the current
installer/host.nix — and nowhere for snapper to write, because disk-config.nix
is a copy made in your repo at install time and disko only ever ran once.
NixOS’ snapper module does not create .snapshots itself
(nixpkgs#34595), so the timers
fire and nothing is ever snapshotted.
omarchy snapshot refuses on such a machine rather than letting it look like it
worked, and nix run github:olafkfreund/nixarchy#doctor names it too. Both print
this fix. It is done once, by hand, because creating btrfs subvolumes on a
running machine from an activation script is exactly the kind of thing a rebuild
should never do.
First create them, at the top level of the filesystem beside @ and @home —
not inside the running root, which is a different place with the same name:
sudo mount -o subvol=/ /dev/mapper/cryptroot /mnt # your root device; `findmnt -no SOURCE /` names it
sudo btrfs subvolume create /mnt/@snapshots
sudo btrfs subvolume create /mnt/@home-snapshots
sudo umount /mnt
Then declare them in your own /etc/nixos/disk-config.nix, inside the
subvolumes block:
"@snapshots" = {
mountpoint = "/.snapshots";
mountOptions = [ "noatime" ];
};
"@home-snapshots" = {
mountpoint = "/home/.snapshots";
mountOptions = [ "noatime" ];
};
and rebuild — nh os switch — which is what writes the mount units. That order
matters: a mount unit for a subvolume that does not exist yet fails at boot.
The declaration is what makes it survive a reinstall; the two commands alone
would be undone by the next one.
The one command that deletes the safety net
nix-collect-garbage -d
-d deletes every generation but the current one. The disk space it frees is
exactly the rollback history. Run it when you mean to, after the current
system has been good for a while, and not as a reflex to free space. Without
-d, nix-collect-garbage removes only store paths no generation references,
which is safe.
Direct boot, upstream’s Setup > Direct Boot, is a Limine feature and has no meaning here; your boot loader’s own timeout setting is the equivalent.