docs(migration): #76 slice 1 — enable-hibernation runbook
Some checks failed
Check / eval (push) Has been cancelled
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:
@@ -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)**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user