diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index cef15f2..c611493 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -72,7 +72,9 @@ interplay handled in `modules/nixos/default.nix` / `modules/home/idle.nix`. **Remaining slices:** -1. **Docs** — `docs/MIGRATION.md` hibernation-enable runbook (V0). +1. ~~**Docs** — `docs/MIGRATION.md` hibernation-enable runbook (V0).~~ ✓ + shipped 2026-07-10 (§ "Enabling hibernation on an existing machine"; + commands verified live against the dev machine's LUKS+@swap layout). 2. **Menu** — Hibernate row: notify-on-failure when no swap configured (behavioral, V1/V2). Keep the row unconditional. 3. **Verify (required)** diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index 7ed2bcd..42f91fd 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -17,6 +17,24 @@ Template: --- +## 2026-07-10 — #76 slice 1: MIGRATION.md hibernation runbook +- **Task:** BACKLOG #76 remaining slice 1 — docs runbook for enabling + hibernation on an existing machine (per the settled "docs not tool" call). +- **Did:** Added § "Enabling hibernation on an existing machine (no reinstall)" + to `docs/MIGRATION.md`: create `@swap` subvol (subvolid=5) + `mkswapfile` + (NOCOW), read `map-swapfile -r` offset, wire `fileSystems."/swap"` + + `swapDevices` + `resumeDevice` + `resume_offset` into system.nix. Flagged + the mount as required on the hand-edit path (installer gets it from + disko-gen hardware-config). Notes zram-priority reservation, no-LUKS + variant, `swap=0` opt-out. Worked example = this dev machine. +- **Verified:** **V0** — read-only commands run live against this machine's + real LUKS(`cryptroot`)+btrfs(`@`)+`/swap/swapfile` layout (RAM 31G, fsuuid, + `findmnt SOURCE`, `mkswapfile` flags, zram@100 vs swapfile@-1 confirms the + design intent). `nix flake check --no-build` green. +- **Pending:** slice 2 — Hibernate row notify-on-failure (V1/V2); then the + V2 hibernate VM test + V3 laptop. +- **Next suggestion:** slice 2 (menu notify-on-failure). + ## 2026-07-10 — #76 hibernation design calls (no commit yet → this bookkeeping) - **Task:** BACKLOG #76 design slice — Bernardo made the open calls. - **Did:** Recon showed the hibernation half is **already built for new diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index c2e10b9..f3782e9 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -301,6 +301,107 @@ lock). --- +## 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 |