The machine flake's git-tracked settings file is system state, not "theme" only — rename it to state.json. CLI becomes nomarchy-state-sync with a nomarchy-theme-sync symlink for scripts and muscle memory. Eval (mkFlake, doctor, lifecycle) still accepts theme-state.json; the next write migrates to state.json and removes the legacy file. Documented in MIGRATION.md; drop the CLI alias after release notes.
19 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)
# Auto-login is NOT set here: it lives in state.json
# (settings.greeter.autoLogin) so System › Auto-login can move it — a line
# here would outrank the state and pin it. Turn it on after the first boot
# with `nomarchy-autologin on` (what nomarchy-install seeds on encrypted
# installs: the LUKS passphrase already gates access).
# 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, …
];
}
state.json
Copy the template's 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 BEFORE the first graphical login.
# Hyprland without an HM generation shows the yellow "autogenerated
# config" banner (and no Nomarchy theming) — finish this step first.
home-manager switch --flake ~/nomarchy-migrate#bernardo -b bak
Log into Hyprland and sanity‑check: Waybar renders, SUPER+M opens the
menu, theming is coherent, SUPER+? shows the cheatsheet, no yellow
Hyprland autogenerated banner. Confirm the machine‑specific things you
care about still work — suspend/hibernate, the AMD GPU (vainfo →
radeonsi), display brightness.
You should also get a one-shot You're set toast (menu / themes /
keys). If it never appears: systemctl --user status nomarchy-first-boot
and re-try with
nomarchy-state-sync set settings.firstBootShown false --no-switch
then log out/in.
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 a preset (writesstate.json). - Snapshots:
nomarchy-menu→ System → Snapshots should see your existing@snapshotssubvolume. - Fingerprint:
fingerprint.enable = trueonly starts fprintd (enrollment). Login/sudo finger auth isfingerprint.pamand is opt-in — leave it commented until you've enrolled. NixOS defaults PAM on whenever fprintd is enabled; Nomarchy forces PAM to follow thepamflag, but only after a system rebuild. Verify withgrep pam_fprintd /etc/pam.d/sudo(should be empty when pam is off). With pam on, the prompt accepts password or finger in parallel by default (fingerprint.parallel = falsefor stock sequential) — see HARDWARE.md §5. - Browser profiles: Nomarchy does not manage Chromium/Firefox state.
Bookmarks/extensions live under
~/.config/chromium(or~/.config/google-chrome/ ungoogled paths if that was your previous browser). If your old Home Manager config declaredprograms.chromium.extensions, carry that block intohome.nixbefore first launch — HM installs those viaExternal Extensions/*.json, and when the JSONs vanish Chromium treats every extension as externally uninstalled and deletes it together with its stored data (Local Extension Settings— wallet vaults, password-manager pairings). Bookmarks survive, which makes it look minor; it isn't. Recovery: close the browser, copyDefault/Local Extension Settings/<id>(plus anyDefault/IndexedDB/chrome-extension_<id>_*) back from your Phase-0pre-nomarchy-homesnapshot, then reinstall each extension — from the Web Store (ids are stable, so the data reattaches) or by re-declaring the ids if you want them declarative again. - VPN: NetworkManager connections survive under
/etc/NetworkManagerand your home. System › VPN lists NMvpn/wireguardprofiles; import any that lived outside NM. Tailscale is opt-in (nomarchy.services.tailscale.enable). - 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.
State file rename (theme-state.json → state.json, #107)
The machine flake's git-tracked state file is state.json (appearance +
menu settings). Older checkouts may still have theme-state.json.
- Eval:
lib.mkFlakeand the reader accept either name (preferstate.json). - Write:
nomarchy-state-sync(and the menu) always writestate.jsonand remove a leftovertheme-state.jsonso you never have two sources. - CLI:
nomarchy-state-syncis the real name;nomarchy-theme-syncremains a symlink for scripts and muscle memory. Drop the alias after the next stable release notes call it out.
No action required on pull: the next theme apply or menu write migrates you.
To migrate by hand: mv theme-state.json state.json && git add state.json
(and git rm theme-state.json if it was tracked).
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) |