Add an optional swap file for hibernation - #4729
Open
VictorZaca wants to merge 11 commits into
Open
Conversation
The swap option only described zram, so there was nowhere to record that the user also wants a swap file. Wrap it in a SwapConfiguration that carries both. The option is widely used in existing configuration files, so parsing still accepts the two shapes it used to take, a bare bool and a dict of zram keys, and both round-trip through the new serialization.
add_swapfile() defaulted to a hardcoded 4G, which is too small to hibernate most machines and too large for the smallest ones. Size it from MemTotal instead: a hibernation image is a snapshot of the physical memory, so a swap file that size is always big enough to hold one. A MemTotal-sized dd would write tens of gigabytes of zeros before the base install even starts, so hand the allocation and the formatting to mkswap. Check the free space on the target first as well, leaving room for the base install, so an oversized swap file fails with a readable error instead of pacstrap running the root partition out of space midway through.
The physical offset filefrag reports is not the one btrfs resumes from, so the offset the generic path collects is wrong there: the machine boots normally and silently fails to restore. Read it back with btrfs inspect-internal map-swapfile instead, and create the file with btrfs filesystem mkswapfile, which also marks it NODATACOW as swapon requires on btrfs. Put it in a subvolume of its own, because btrfs refuses to snapshot a subvolume that holds an active swap file and the root one has to stay snapshottable for snapper, timeshift and grub-btrfs to keep working. Take the filesystem off the mount rather than off the disk layout, since a pre-mounted configuration carries no partition modifications to look at. Checked against btrfs-progs 7.1 on a root subvolume mounted compress=zstd: map-swapfile reports 140544 where filefrag reports 72960, and snapshotting the root subvolume with swap active only succeeds once the swap file is moved out of it.
add_swapfile() appended resume to the end of HOOKS, which lands it after fsck, and added it unconditionally. Insert it before fsck instead, mirroring how _prepare_encrypt() places its own hook, so it runs once udev and whichever of encrypt and lvm2 the layout pulled in have made the resume device available. Skip it altogether when a HSM device is in use. The initramfs keeps the systemd hook in that case, and that one already installs systemd-hibernate-resume along with the generator that reads resume= and resume_offset= off the kernel command line, so the busybox hook has nothing left to do.
The swap entry was a yes/no question about zram, so there was no way to ask for a swap file. Turn it into a selection over the four combinations of zram and a swap file, keeping zram as the default and the compression algorithm prompt that follows it.
minimal_installation() is what runs mkinitcpio, so the swap file has to be in place before it: the resume hook and the resume= and resume_offset= parameters add_swapfile() collects would otherwise never reach the initramfs. zram keeps its existing position after the base install, since setup_swap() pacstraps zram-generator into the target and needs one to exist.
examples/config-sample.json still documented swap in the shape the option had back when it carried nothing but zram, and the JSON options table still listed it as a plain boolean; write the current one in both. Add tests over SwapConfiguration.parse_arg for every shape the option accepts, old and new, over the json() round trip and over the config file path, so compatibility with config files already out there cannot regress unnoticed. Cover the derived swap file size as well, since rounding it down would leave part of the hibernation image with nowhere to go.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds first-class support for creating and configuring an on-disk swap file suitable for hibernation (including resume kernel params / initramfs hook), while keeping existing zram swap support and maintaining backward compatibility for prior swap config shapes.
Changes:
- Introduces
SwapConfiguration(zram + swapfile) and updates config parsing, menus, and previews accordingly. - Wires
Installer.add_swapfile()into the guided installation flow before initramfs generation, and adds swapfile sizing logic based onMemTotal. - Adds/updates documentation, examples, locales, and tests to cover old/new config formats and size calculation.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_swap_config.py | Adds coverage for swap config backward compatibility, round-tripping, config-file parsing, and swapfile sizing. |
| tests/test_args.py | Updates config parsing expectations to use SwapConfiguration instead of ZramConfiguration. |
| examples/config-sample.json | Updates sample config to new swap: { zram: {...}, swapfile: ... } shape. |
| docs/cli_parameters/config/config_options.csv | Updates user-facing docs for the swap config option. |
| archinstall/scripts/guided.py | Creates swapfile before minimal_installation() when enabled; updates zram setup accessors. |
| archinstall/locales/base.pot | Adds/updates translatable strings for the new swap selection UI and summary. |
| archinstall/lib/models/application.py | Adds SwapConfiguration model with parsing, JSON serialization, and summary output. |
| archinstall/lib/installer.py | Implements swapfile creation, filesystem-specific offset detection, resume hook preparation, and swapfile sizing. |
| archinstall/lib/global_menu.py | Switches menu default/value type to SwapConfiguration and updates swap preview formatting. |
| archinstall/lib/general/system_menu.py | Reworks swap selection UI to support zram-only, swapfile-only, both, or none. |
| archinstall/lib/args.py | Changes ArchConfig.swap type and config parsing to SwapConfiguration. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The row listed zram and swapfile as if they were the accepted values, in the same shape audio_config lists pipewire and pulseaudio. Following that gets a crash rather than a misconfiguration: parse_arg takes a bool or a dict, and 'zram' in arg matches a substring when arg is a string, so "swap": "zram" falls into the dict branch and dies on arg.get with AttributeError. Spell out the dict instead, the way the other dict-valued options in the table already do.
statvfs was called on the target root, which is the wrong filesystem as soon as a caller hands add_swapfile a path on a mount of its own. Nothing in the tree does, the file is always /swap/swapfile or /swapfile, but this is a public method and the signature changed here, so it should hold up for a plugin. Walk up to the nearest existing ancestor rather than the parent: the btrfs default puts the file in /swap, which does not exist yet at this point, and a nested directory on a separate mount would fall back to the target root and measure the wrong filesystem again. Path.parents starts at the parent and / is always there, so the walk always terminates.
The message asserted that snapshots would be blocked, but the directory may already be a dedicated subvolume, in which case nothing is wrong: the only snapshots that fail are of the swap subvolume itself, which nobody takes. Name the condition instead of claiming the outcome.
pylint flagged W0621: installer.py already imports mount from disk.utils, so naming the local for the measured filesystem the same thing shadowed it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
archinstall only sets up zram today, and zram cannot hold a hibernation image: the compressed pages live in the RAM you are trying to write out. So hibernation is a manual job after the install — create the file, find the offset, add two kernel parameters, add the
resumehook, rebuild the initramfs. #994 has been asking for this since 2022, and #2371 and #2450 are the same request.Installer.add_swapfile()has also been sitting in the tree unused since #1558 in March 2023. This wires it up and fixes what would have broken if anything had called it.The menu
zram and the swap file can be on together, which is probably what most people want. zram stays at priority 100 and does the day-to-day swapping; the file sits at -1 so a hibernation image has somewhere to go.
The parts worth reviewing
The file is sized at
MemTotaland created beforeminimal_installation(), so the hook and the kernel parameters are in place before the initramfs is built.On btrfs it gets a subvolume of its own at
/swap. btrfs will not snapshot a subvolume that holds an active swap file, and turning on hibernation should not make/unsnapshottable as a side effect.The offset comes from
btrfs inspect-internal map-swapfile -r, notfilefrag. On btrfs the two disagree (140544 vs 72960 on my test VM) andfilefrag's number will not resume. It fails silently, the machine just cold-boots, so it is easy to get wrong.swapused to hold the zram configuration directly. Both old shapes still parse (trueand{"enabled": ..., "algorithm": ...}); the new one is{"zram": {...}, "swapfile": true}.tests/test_swap_config.pycovers all of them plus a round trip throughArchConfigHandler.add_swapfile()'s signature changed. Nothing in the tree calls it, but a plugin might.Two things I deliberately left alone:
only_hd.pyenables the swap menu item without ever acting on it, which is pre-existing and equally true of zram, andschema.jsonhas noswapkey and is currently invalid JSON anyway.Testing
49 tests pass,
ruffandmypy --strictare clean on each commit individually, andlocales_generator.sh checkpasses. Hibernation is verified end to end in QEMU on btrfs with systemd-boot and UKI — the full round trip is in the first comment below.Closes #994
Closes #2371
Refs #2450, #4169