# 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 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 | |---|---|---| | **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](https://nixos.org/manual/nixos/stable/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 on‑disk/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: ```console $ nixos-option system.stateVersion # or: nix eval .#nixosConfigurations..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 belt‑and‑suspenders, not a necessity for file safety. --- ## Phase 0 — Safety net (nothing changes yet) ```bash # 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. ```bash 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. ```bash cp /etc/nixos/hosts/TuringMachine/hardware-configuration.nix ./hardware-configuration.nix ``` ### `flake.nix` — one `mkFlake` call (from the template) ```nix { 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**. ```nix { 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 theme-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.nix` ryzenadj stack, the `lanzaboote` > input, and the `power-max`/`power-stealth` scripts. PPD's > performance/balanced/power‑saver 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). ```nix { 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 ```bash 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 ```bash # 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-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 / 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 (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). With pam on, the prompt accepts password *or* finger in parallel by default (`fingerprint.parallel = false` for 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 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: close the browser, copy `Default/Local Extension Settings/` (plus any `Default/IndexedDB/chrome-extension__*`) back from your Phase-0 `pre-nomarchy-home` snapshot, 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/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 agenix‑managed secrets for a service, layer `agenix` back into `system.nix` as 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: ```bash # 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-nomarchy` and the old per‑host modules you cut (ryzenadj power‑management, 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 ```bash # 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 ```bash # 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 ```bash 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: ```nix # Hibernation: encrypted swapfile on the @swap subvolume. fileSystems."/swap" = { device = "/dev/disk/by-uuid/"; fsType = "btrfs"; options = [ "subvol=@swap" "noatime" ]; }; swapDevices = [{ device = "/swap/swapfile"; }]; boot.resumeDevice = "/dev/disk/by-uuid/"; boot.kernelParams = [ "resume_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 ```bash 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) |