Files
Nomarchy/docs/MIGRATION.md
Bernardo Magri 42542334e8
Some checks failed
Check / eval (push) Has been cancelled
docs: backfill 05bab55 journal entry; real cause in MIGRATION browser note
The migration session crashed before journaling. Follow-up diagnosis:
extensions vanished because programs.chromium.extensions wasn't carried
into the downstream flake — Chromium external-uninstall deletes the
extensions AND their Local Extension Settings. Rewrite the browser
migration note with the mechanism and the snapshot recovery path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:35:07 +01:00

18 KiB
Raw Blame History

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 Nomarchybased 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, systemdboot — 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 test never 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
systemdboot bootctl status → "Product: systemdboot" 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 onerelease 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

  1. Never bump system.stateVersion. It is an ondisk/service compatibility marker tied to when the machine was first installed — not the nixpkgs version. Nomarchy's template ships 26.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.)

  2. /home is never touched by an activation. A nixos-rebuild switch swaps the system generation; your data subvolume is untouched. The Phase 0 snapshot is beltandsuspenders, 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 knowngood generation; it remains in the systemdboot 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.nix ryzenadj stack, the lanzaboote input, and the power-max/power-stealth scripts. PPD's performance/balanced/powersaver profiles (switchable from the Waybar battery/profile icons and nomarchy-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 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 sanitycheck: Waybar renders, SUPER+M opens the menu, theming is coherent, SUPER+? shows the cheatsheet, no yellow Hyprland autogenerated banner. Confirm the machinespecific 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-theme-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 get works and the Waybar battery / powerprofile icons open the power menu. Your ryzenadj scripts are gone; if you miss a specific TDP behaviour, that's a followup, not a blocker.
  • Theme: nomarchy-menu theme → pick a preset (writes theme-state.json).
  • Snapshots: nomarchy-menu → System → Snapshots should see your existing @snapshots subvolume.
  • Fingerprint: fingerprint.enable = true only starts fprintd (enrollment). Login/sudo finger auth is fingerprint.pam and is opt-in — leave it commented until you've enrolled. NixOS defaults PAM on whenever fprintd is enabled; Nomarchy forces PAM to follow the pam flag, but only after a system rebuild. Verify with grep pam_fprintd /etc/pam.d/sudo (should be empty when pam is off).
  • 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 declared programs.chromium.extensions, carry that block into home.nix before first launch — HM installs those via External 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: re-declare the extension ids, close the browser, and copy Default/Local Extension Settings/<id> (plus any Default/IndexedDB/chrome-extension_<id>_*) back from your Phase-0 pre-nomarchy-home snapshot before relaunching.
  • VPN: NetworkManager connections survive under /etc/NetworkManager and your home. System VPN lists NM vpn/wireguard profiles; import any that lived outside NM. Tailscale is opt-in (nomarchy.services.tailscale.enable).
  • Secrets/services: if you relied on agenixmanaged secrets for a service, layer agenix back into system.nix as a machinespecific 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 systemdboot 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 prenomarchy commit) is your old config verbatim.

/home is untouched by all of the above.


Postmigration 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-nomarchy and the old perhost modules you cut (ryzenadj powermanagement, lanzaboote, the nomarchy-state.nix prototype).
  • 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. /home is 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 systemdboot, 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 systemdboot (unchanged — already matched)
Filesystem LUKS + btrfs, existing subvolumes reused (incl. snapshot subvols)