Add a self-contained "Enabling hibernation on an existing machine (no reinstall)" section to docs/MIGRATION.md, per the settled #76 call to document migration rather than ship a tool. Covers: @swap subvolume creation (subvolid=5), btrfs mkswapfile (NOCOW), reading the resume offset via map-swapfile, and wiring fileSystems."/swap" + swapDevices + resumeDevice + resume_offset into system.nix. Flags the /swap mount as required on the hand-edit path (a fresh install inherits it from disko-generated hardware-config). Notes the zram-priority reservation, a no-LUKS variant, and the swap=0 opt-out. Verification: V0. All read-only commands run live against this machine's LUKS(cryptroot)+btrfs(@)+/swap/swapfile layout; nix flake check --no-build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
16 KiB
Migrating an existing NixOS machine to Nomarchy (no reinstall)
If your machine already runs NixOS, you do not need the installer or a
reformat to adopt Nomarchy. Nomarchy is a flake; "installing" it onto an
existing NixOS box means pointing nixos-rebuild at a Nomarchy‑based flake
that reuses your current hardware-configuration.nix. Nothing repartitions,
and your /home is never written to by an activation.
This guide is written generically, with TuringMachine — a Lenovo AMD Ryzen 7840U / Radeon 780M laptop, LUKS + btrfs, systemd‑boot — as the concrete worked example. Substitute your own values where called out.
The promise: every step below is reversible. You keep three independent rollback nets (NixOS generations, a btrfs snapshot, and the fact that
nixos-rebuild testnever changes the boot default), and your files live on a separate subvolume that no config switch touches.
0. Is your machine a good candidate?
Migration is cleanest when your machine already matches Nomarchy's assumptions. Check each:
| Nomarchy expects | Check it | If it differs |
|---|---|---|
| systemd‑boot | bootctl status → "Product: systemd‑boot" |
GRUB works too; keep your loader config in system.nix |
btrfs with @/@home subvolumes |
findmnt -t btrfs |
ext4/xfs boot fine — you just don't get snapshots |
@snapshots + @home-snapshots subvols |
in findmnt output |
snapper features need them; create them or skip snapshots |
| LUKS (optional, themed prompt) | lsblk -f shows crypto_LUKS |
none — LUKS is optional |
| Already a flake config | test -f /etc/nixos/flake.nix |
fine either way; you'll write a fresh flake regardless |
Installer vs migration snapshot layout. A fresh Nomarchy install
(disko) creates a top-level @snapshots subvolume mounted at
/.snapshots, then a first-boot oneshot makes a nested
/home/.snapshots under @home for snapper's home timeline — it does
not create a separate top-level @home-snapshots. Migration machines
(e.g. TuringMachine) may already use top-level @snapshots and
@home-snapshots; that is fine. Snapper only needs a .snapshots path
under each tracked subvolume (/ and /home), so either layout works —
reuse what you have, or create the missing pieces if you want snapper
without reformatting.
TuringMachine matches all of these, including the @snapshots /
@home-snapshots subvolumes — so snapper works with zero disk work.
Version note. Nomarchy pins nixos-26.05. If you're on an older release
(TuringMachine is on 25.11), the migration folds a one‑release upgrade
into the switch. That's normal and supported — read the
NixOS 26.05 release notes
for option/package renames, and lean on generations if something regresses.
1. The two rules that protect your data
-
Never bump
system.stateVersion. It is an on‑disk/service compatibility marker tied to when the machine was first installed — not the nixpkgs version. Nomarchy's template ships26.05; you must change it back to your machine's original value. Find yours:$ nixos-option system.stateVersion # or: nix eval .#nixosConfigurations.<host>.config.system.stateVersion "24.11"(TuringMachine:
24.11.) -
/homeis never touched by an activation. Anixos-rebuild switchswaps the system generation; your data subvolume is untouched. The Phase 0 snapshot is belt‑and‑suspenders, not a necessity for file safety.
Phase 0 — Safety net (nothing changes yet)
# Read-only btrfs snapshots of root and home — instant rollback targets.
sudo btrfs subvolume snapshot -r / /.snapshots/pre-nomarchy-root
sudo btrfs subvolume snapshot -r /home /home/.snapshots/pre-nomarchy-home
# Freeze your current config as a clean git baseline.
cd /etc/nixos && git add -A && git commit -m "pre-nomarchy baseline" || true
You are currently booted in a known‑good generation; it remains in the systemd‑boot menu throughout. Worst case at any later step: reboot and pick it.
Phase 1 — Build the Nomarchy flake alongside (no switch)
Stand the new config up in a working directory and build it without activating. This is the real safety line: iterate here until it builds green before anything touches the running system.
git clone https://git.bemagri.xyz/bernardo/nomarchy.git ~/nomarchy-migrate
cd ~/nomarchy-migrate
# Or start from the downstream template:
# nix flake init -t "git+https://git.bemagri.xyz/bernardo/nomarchy.git?ref=v1"
# (produces flake.nix/system.nix/home.nix/…)
A Nomarchy downstream flake owns exactly five files. Assemble them:
hardware-configuration.nix — reuse yours unchanged
Copy your existing hardware config in verbatim. This is what preserves your disks, LUKS, btrfs subvolumes and swap — the reason no reinstall is needed.
cp /etc/nixos/hosts/TuringMachine/hardware-configuration.nix ./hardware-configuration.nix
flake.nix — one mkFlake call (from the template)
{
description = "TuringMachine — Nomarchy";
inputs.nomarchy.url = "git+https://git.bemagri.xyz/bernardo/nomarchy.git?ref=v1";
outputs = { nomarchy, ... }:
nomarchy.lib.mkFlake {
src = ./.;
username = "bernardo"; # <- your login name
# Optional nixos-hardware profile(s) for your model. For an AMD laptop:
# hardwareProfile = [ "common-cpu-amd-pstate" "common-gpu-amd" "common-pc-laptop-ssd" ];
# Names: https://github.com/NixOS/nixos-hardware (verify before use)
# Full hardware story (firmware, fingerprint, unsupported machines):
# docs/HARDWARE.md in the Nomarchy repo
};
}
system.nix — machine specifics + your decisions
This is where your three migration decisions land: PPD power (no ryzenadj), no Secure Boot, and the stateVersion override.
{ pkgs, username, ... }:
{
# Plain systemd-boot — no lanzaboote / Secure Boot.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "TuringMachine";
time.timeZone = "Europe/London"; # your zone
i18n.defaultLocale = "en_US.UTF-8";
users.users.${username} = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "video" "input" ];
};
# ── Power: Nomarchy's PPD (drops all custom ryzenadj/TLP tuning) ──────
nomarchy.system.power = {
enable = true;
backend = "ppd"; # power-profiles-daemon
laptop = true;
batteryChargeLimit = 80; # optional longevity cap
};
# ── AMD 7840U / Radeon 780M ──────────────────────────────────────────
nomarchy.hardware.amd.enable = true; # amd-pstate + radeonsi VA-API
# nomarchy.hardware.amd.rocm.enable = true; # opt-in GPU compute (multi-GB)
# CRITICAL: keep your ORIGINAL install's value — never Nomarchy's 26.05.
system.stateVersion = "24.11";
}
Dropped on purpose (decisions 1 & 2): your old
modules/services/power-management.nixryzenadj stack, thelanzabooteinput, and thepower-max/power-stealthscripts. PPD's performance/balanced/power‑saver profiles (switchable from the Waybar battery/profile icons andnomarchy-menu) replace them.
home.nix — your apps on top of Nomarchy's desktop
Start from the template's home.nix (it ships the default app set) and add
your personal packages/config. Carry over anything you still want (e.g. your
emacs setup).
{ pkgs, lib, ... }:
{
# Nomarchy hardcodes home.stateVersion = "26.05". Moving home-manager's
# stateVersion is low-risk, but if you want to pin your original:
home.stateVersion = lib.mkForce "24.11";
home.packages = with pkgs; [
# your extras — e.g. emacs, language toolchains, …
];
}
theme-state.json
Copy the template's theme-state.json (or let nomarchy-menu theme write
it after the switch). Your old nomarchy-state.nix prototype (schema
nomarchy.theme = "nord" …) is retired — the current distro uses this
JSON. nord is a shipped Nomarchy theme, so you lose nothing.
The build gate
nixos-rebuild build --flake ~/nomarchy-migrate#default
Zero activation — this only evaluates and builds the system closure. Fix any eval/build error here, in isolation, before touching the running machine. Expect to resolve a few 25.11→26.05 option renames.
Phase 2 — Reversible activation
# Activates now, but does NOT become the boot default. If the session
# breaks, REBOOT and you are back in your old generation, untouched.
sudo nixos-rebuild test --flake ~/nomarchy-migrate#default
# Bring the desktop (home-manager) up:
home-manager switch --flake ~/nomarchy-migrate#bernardo
Log into Hyprland and sanity‑check: Waybar renders, SUPER+M opens the
menu, theming is coherent, SUPER+? shows the cheatsheet. Confirm the
machine‑specific things you care about still work — suspend/hibernate, the
AMD GPU (vainfo → radeonsi), display brightness.
If anything is wrong: reboot → old generation. Nothing is committed as default yet.
Phase 3 — Reconcile
- Power: verify
powerprofilesctl getworks and the Waybar battery / power‑profile icons open the power menu. Your ryzenadj scripts are gone; if you miss a specific TDP behaviour, that's a follow‑up, not a blocker. - Theme:
nomarchy-menu theme→ pick nord (writestheme-state.json). - Snapshots:
nomarchy-menu→ System → Snapshots should see your existing@snapshotssubvolume. - Secrets/services: if you relied on agenix‑managed secrets for a
service, layer
agenixback intosystem.nixas a machine‑specific import (Nomarchy doesn't manage secrets). If you don't need them, leave them out — this is a full cutover.
Phase 4 — Cutover
Once a test boot is solid:
# Move the flake to its canonical home and make it the boot default.
mv ~/nomarchy-migrate ~/.nomarchy
sudo nixos-rebuild switch --flake ~/.nomarchy#default
home-manager switch --flake ~/.nomarchy#bernardo
# Point /etc/nixos at the flake (optional but conventional).
sudo mv /etc/nixos /etc/nixos.pre-nomarchy
sudo ln -s ~/.nomarchy /etc/nixos
From here you're on the standard Nomarchy update flow: sys-update (lock +
system) then home-update (desktop) — always in that order (a lock bump
before the home switch, or desktop changes are skipped against the old
lock).
Rollback, at any point
| Net | How |
|---|---|
| NixOS generation | Reboot → pick the previous entry in the systemd‑boot menu. |
nixos-rebuild test |
Never sets the boot default; a reboot reverts it. |
| btrfs snapshot | Restore /.snapshots/pre-nomarchy-root (see docs/RECOVERY.md). |
| git baseline | /etc/nixos.pre-nomarchy (and the pre‑nomarchy commit) is your old config verbatim. |
/home is untouched by all of the above.
Post‑migration cleanup (once you're confident)
- Delete the safety snapshots:
sudo btrfs subvolume delete /.snapshots/pre-nomarchy-root(and the home one). - Remove
/etc/nixos.pre-nomarchyand the old per‑host modules you cut (ryzenadj power‑management, lanzaboote, thenomarchy-state.nixprototype). - Prune old generations:
sudo nix-collect-garbage -d.
Enabling hibernation on an existing machine (no reinstall)
New Nomarchy installs are hibernation-ready out of the box — the installer
defaults the swapfile to = RAM on its own @swap subvolume and wires the
resume offset. This section is for machines that have no hibernate swap:
one installed with swap = 0, one migrated here whose reused
hardware-configuration.nix carries no swapfile, or an older install
predating the default. If swapon --show already lists /swap/swapfile,
you have nothing to do.
Hibernation writes RAM to disk, and zram (compressed RAM) can't hold that
image across a power-off — so you need a real disk swap ≥ the RAM you use.
Nomarchy puts it on an @swap subvolume inside the encrypted volume, so
the image is encrypted at rest and the initrd LUKS unlock gates resume.
Reversible: this is one subvolume plus four config lines. Remove them and rebuild — or just boot the previous generation — to undo.
/homeis never touched.
Worked example below is this machine: LUKS mapper cryptroot, btrfs @.
Substitute your own device/UUID/offset where shown.
1. Size and locate
# Swap = RAM, rounded up to whole GiB (matches the installer default).
ram_gb=$(awk '/MemTotal/ {print int(($2 + 1048575) / 1048576)}' /proc/meminfo)
# The decrypted BTRFS device backing / (strip the [subvol] suffix) and its
# filesystem UUID — the same UUID resolves to the mapper once initrd unlocks.
dev=$(findmnt -no SOURCE / | sed 's/\[.*//') # e.g. /dev/mapper/cryptroot
fsuuid=$(findmnt -no UUID /) # e.g. d8e2b02d-…
echo "swap=${ram_gb}G dev=$dev uuid=$fsuuid"
2. Create the @swap subvolume + swapfile
# Create the subvolume at the BTRFS top level (subvolid=5), beside @, @home…
sudo mkdir -p /mnt/btrfs-top
sudo mount -o subvolid=5 "$dev" /mnt/btrfs-top
sudo btrfs subvolume create /mnt/btrfs-top/@swap
sudo umount /mnt/btrfs-top && sudo rmdir /mnt/btrfs-top
# Mount it and create the swapfile. `mkswapfile` applies the BTRFS NOCOW
# requirements automatically (a copy-on-write swapfile would corrupt).
sudo mkdir -p /swap
sudo mount -o subvol=@swap,noatime "$dev" /swap
sudo btrfs filesystem mkswapfile --size "${ram_gb}g" --uuid clear /swap/swapfile
3. Read the resume offset
sudo btrfs inspect-internal map-swapfile -r /swap/swapfile # prints the offset
4. Wire it into system.nix
Add these to your machine's system.nix, substituting your $fsuuid and the
offset from step 3. The fileSystems."/swap" mount is required on this
path — a fresh install inherits it from disko-generated
hardware-configuration.nix, but a hand edit must declare it so /swap is
mounted before swap activates:
# Hibernation: encrypted swapfile on the @swap subvolume.
fileSystems."/swap" = {
device = "/dev/disk/by-uuid/<fsuuid>";
fsType = "btrfs";
options = [ "subvol=@swap" "noatime" ];
};
swapDevices = [{ device = "/swap/swapfile"; }];
boot.resumeDevice = "/dev/disk/by-uuid/<fsuuid>";
boot.kernelParams = [ "resume_offset=<offset>" ];
zram stays on by default (Nomarchy sets zramSwap at priority 100 in
modules/nixos/oom.nix); this disk swapfile sits lower, so day-to-day
paging stays in compressed RAM and the file is reserved for the hibernate
image — exactly the intent.
5. Rebuild and test
sudo nixos-rebuild switch --flake ~/.nomarchy#default
systemctl hibernate # or nomarchy-menu → Power → Hibernate
The machine powers off; on the next boot you unlock LUKS once and land back
in your session. If you get a fresh boot instead, the usual cause is a
wrong resume_offset or a swapfile smaller than in-use RAM — re-read the
offset (step 3) and confirm swap ≥ RAM.
No LUKS? Same steps; dev/$fsuuid point at the plain BTRFS partition
and the image is unencrypted at rest. swap = 0 opt-out stays valid — if
you don't want hibernation, skip all of this; the Power-menu Hibernate row
just reports that no swap is configured.
TuringMachine — the decisions, at a glance
| Item | Choice |
|---|---|
| Power | Nomarchy PPD (backend = "ppd"); all ryzenadj/TLP tuning dropped |
| Secure Boot | Off — plain systemd‑boot, lanzaboote dropped |
| Scope | Full cutover to Nomarchy's structure |
system.stateVersion |
24.11 (preserved from original install) |
| Hardware | nomarchy.hardware.amd.enable = true (7840U / Radeon 780M) |
| Bootloader | systemd‑boot (unchanged — already matched) |
| Filesystem | LUKS + btrfs, existing subvolumes reused (incl. snapshot subvols) |