docs(migration): #76 slice 1 — enable-hibernation runbook
Some checks failed
Check / eval (push) Has been cancelled

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>
This commit is contained in:
Bernardo Magri
2026-07-10 19:34:14 +01:00
parent 858362cfcc
commit 8c048a2692
3 changed files with 122 additions and 1 deletions

View File

@@ -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)**

View File

@@ -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

View File

@@ -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/<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
```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 |