Full documentation for refind-btrfs-snapshots. For installation and quick start, see the README.
- How It Works
- Boot Mode Detection
- Boot Layouts
- Commands
- Configuration Reference
- Kernel Detection & Staleness
- Include File Management
- Systemd Integration
- Integration Examples
- Time and Format Handling
- Troubleshooting
- Bootloader Coverage
- Development
- Detection Phase: Discovers btrfs volumes, snapshots, and ESP location
- Boot Planning Phase: Parses each snapshot's
/etc/fstabto determine its boot mode — ESP mode (kernel on a separate partition) or btrfs mode (kernel inside the snapshot) - Kernel Scan Phase: For ESP-mode snapshots, scans the ESP for boot images, groups them into boot sets, inspects kernel binaries for version info, and checks each snapshot for matching kernel modules. For btrfs-mode snapshots, discovers kernels directly inside the snapshot's
/bootdirectory. - Analysis Phase: Determines optimal configuration method (
refind_linux.confvs include files) - Generation Phase: Creates boot entries with proper kernel parameters and initrd paths, applying staleness actions for ESP-mode snapshots as configured
- Validation Phase: Shows unified diff of all changes before applying
- Application Phase: Updates configuration files atomically
Boot mode is determined per-snapshot by inspecting each snapshot's own /etc/fstab. This is important because a system's boot configuration can change over time — some snapshots may have been taken when /boot was a separate ESP partition, and others when /boot was part of the btrfs subvolume.
When a snapshot's fstab shows /boot mounted on a non-btrfs filesystem (typically vfat), the snapshot is in ESP mode:
- Kernel and initramfs are loaded from the ESP (EFI System Partition)
- Staleness checking applies — if the snapshot's kernel modules don't match the ESP's kernel, the configured
stale_snapshot_actionis applied - Submenu entries only override
options(to set the snapshot'srootflags=subvol=...)
submenuentry "Arch Linux (2025-01-15T10:00:00Z)" {
options "... rootflags=subvol=/@/.snapshots/42/snapshot ..."
}
When a snapshot's fstab shows /boot is not separately mounted (or is mounted from the same btrfs filesystem), the snapshot is in btrfs mode:
- The snapshot contains its own kernel and initramfs in its
/bootdirectory - rEFInd's btrfs EFI driver loads these directly from the snapshot subvolume
- Staleness is impossible — the kernel and modules are always in sync within the snapshot
- Submenu entries override
volume,loader, andinitrdto point into the snapshot
submenuentry "Arch Linux (2025-02-14T10:00:00Z)" {
volume ARCH_ROOT
loader /@/.snapshots/73/snapshot/boot/vmlinuz-linux
initrd /@/.snapshots/73/snapshot/boot/initramfs-linux.img
options "... rootflags=subvol=/@/.snapshots/73/snapshot ..."
}
A single rEFInd menu entry can contain both ESP-mode and btrfs-mode submenus. This happens naturally when a system transitions between boot configurations — older snapshots retain their original mode. The status command shows a BOOT column indicating each snapshot's detected mode.
Note: Boot mode detection requires rEFInd's btrfs EFI driver (
btrfs_x64.efi) to be installed for btrfs-mode entries to work. This driver is included with rEFInd and is typically loaded automatically.
Boot Layout is orthogonal to Boot Mode. Boot Mode describes how /boot is mounted within a snapshot (ESP vs btrfs); Boot Layout describes how the kernel artefacts are arranged on disk (Split vs BLS vs UKI). A single boot set has exactly one layout; a system can host multiple boot sets with different layouts simultaneously.
The classic layout: kernel, initramfs, and microcode live as separate files (typically under /boot) referenced from refind.conf or refind_linux.conf. This is what most installations use today.
A drop-in .conf file under <esp>/loader/entries/*.conf describes the entry. The .conf names a linux (kernel) path, one or more initrd paths, and an options string. The referenced files live elsewhere on the ESP. Spec: https://uapi-group.org/specifications/specs/boot_loader_specification/#type-1-boot-loader-specification-entries.
rEFInd does not parse BLS
.conffiles. This tool reads them as a discovery signal — we use them to learn the canonical kernel and cmdline for a kernel — but the rEFInd output we generate references the underlying loose files. Output shape for BLS layout is identical to Split layout. If your system boots exclusively via systemd-boot, see Bootloader Coverage.
A single PE/EFI binary at <esp>/EFI/Linux/<name>.efi containing the kernel, initramfs, cmdline, and OS-release metadata. The systemd-stub launches the embedded kernel with the embedded cmdline. Spec: https://uapi-group.org/specifications/specs/boot_loader_specification/#type-2-efi-unified-kernel-images.
Discovery only — not snapshot-bootable. We detect and inspect UKIs (they show up in list bootsets, status, and kernel-spy, and their embedded kernel version participates in staleness detection), but we do not produce snapshot boot entries for UKI boot sets. A snapshot-bootable cmdline is rootflags=subvol=<snap>,subvolid=<id>, and on a UKI that lives inside the signed .cmdline PE section — there's no generally-reliable boot-loader-side override, so making a snapshot bootable would require rewriting (and, under Secure Boot, re-signing) the UKI per snapshot. That's tracked as a planned standalone uki-btrfs-snapshots binary — see WISHLIST.md.
Generate rEFInd boot entries for btrfs snapshots.
sudo refind-btrfs-snapshots generate [flags]Flags:
| Flag | Short | Description |
|---|---|---|
--config-path |
Path to rEFInd main config file | |
--count |
-n |
Number of snapshots to include (0 = all) |
--dry-run |
Show what would be done without making changes | |
--esp-path |
-e |
Path to ESP mount point |
--force |
Force generation even if booted from snapshot | |
--generate-include |
-g |
Force generation of refind-btrfs-snapshots.conf include file |
--yes |
-y |
Automatically approve all changes without prompting |
Examples:
# Preview changes for top 5 snapshots
sudo refind-btrfs-snapshots generate --dry-run --count 5
# Generate include file and auto-approve
sudo refind-btrfs-snapshots generate -g -y
# Use custom rEFInd config path with debug logging
sudo refind-btrfs-snapshots generate --config-path /path/to/refind.conf --log-level debug
# Force operation even if booted from snapshot
sudo refind-btrfs-snapshots generate --force --dry-runInventory commands for btrfs volumes, snapshots, and the ESP. None of these compute kernel staleness — use status for the snapshot↔bootset compatibility matrix.
sudo refind-btrfs-snapshots list [command] [flags]Subcommands:
| Subcommand | Description |
|---|---|
volumes |
List all btrfs filesystems/volumes |
snapshots |
List all snapshots for detected volumes (no ESP scan; works offline) |
bootsets |
List detected boot image sets on the ESP |
Flags (list volumes):
| Flag | Description |
|---|---|
--json |
Output in JSON format |
--show-all-ids |
Show all device identifiers (UUID, PARTUUID, LABEL, etc.) |
Flags (list snapshots):
| Flag | Description |
|---|---|
--json |
Output in JSON format |
--show-size |
Calculate and show snapshot sizes (slower) |
--show-volume |
Show parent volume column (useful for multi-filesystem setups) |
--volume <id> |
Show snapshots only for specific volume UUID or device |
--search-dirs |
Override snapshot search directories |
Flags (list bootsets):
| Flag | Description |
|---|---|
--json |
Output in JSON format |
--show-images |
Show individual boot images in addition to boot sets |
Examples:
# List all detected volumes
sudo refind-btrfs-snapshots list volumes
# List snapshots
sudo refind-btrfs-snapshots list snapshots
# List snapshots with sizes and volume column
sudo refind-btrfs-snapshots list snapshots --show-size --show-volume
# Snapshots for a specific volume in JSON
sudo refind-btrfs-snapshots list snapshots --volume <uuid> --json
# Show detected boot sets on the ESP
sudo refind-btrfs-snapshots list bootsets
# Show individual boot images alongside boot sets
sudo refind-btrfs-snapshots list bootsets --show-imagesShow snapshot bootability against detected ESP boot sets. This is the diagnostic command for answering "if my /boot breaks, which of my snapshots are real fallbacks?" — it joins the snapshot inventory with the ESP boot images and renders a compatibility matrix.
Each row is a snapshot; each kernel column shows whether the snapshot's modules match that kernel's version (fresh) or not (stale). Snapshots in btrfs-mode embed their own kernel and are marked n/a — staleness is impossible for them.
sudo refind-btrfs-snapshots status [flags]Flags:
| Flag | Description |
|---|---|
--json |
Output in JSON format |
--unbootable-only |
Show only snapshots that are stale (or unbootable) against every detected boot set |
Examples:
# Full compatibility matrix
sudo refind-btrfs-snapshots status
# Only show snapshots that can't boot from any current kernel
sudo refind-btrfs-snapshots status --unbootable-only
# Machine-readable output for scripting
sudo refind-btrfs-snapshots status --jsonShow version information.
refind-btrfs-snapshots versionConfiguration files are searched in the following order:
--configflag path (highest priority)/etc/refind-btrfs-snapshots.yaml(recommended location)- Built-in defaults (lowest priority)
The ESP detection system uses a three-tier priority system:
esp:
uuid: "A1B2-C3D4"- Use case: Multiple ESPs or specific ESP targeting
- Reliability: Highest — immune to mount point changes
esp:
auto_detect: true- Method: Scans
/proc/mountsand/sys/blockfor ESP characteristics - Detection criteria: VFAT filesystem with ESP partition type (EF00), common mount points (
/boot/efi,/efi,/boot), presence of/EFIdirectory structure - Use case: Standard single-ESP systems
esp:
auto_detect: false
mount_point: "/boot/efi"- Use case: Non-standard ESP locations or troubleshooting
| Category | Option | Default | Description |
|---|---|---|---|
| Snapshot | snapshot.selection_count |
0 |
Number of snapshots to include (0 = all) |
snapshot.search_directories |
["/.snapshots"] |
Directories to scan for snapshots | |
snapshot.max_depth |
3 |
Maximum search depth in snapshot directories | |
snapshot.writable_method |
"toggle" |
Method for writable snapshots: toggle or copy |
|
snapshot.destination_dir |
"/.refind-btrfs-snapshots" |
Directory for copied writable snapshots | |
| ESP | esp.uuid |
"" |
Specific ESP UUID (highest priority) |
esp.auto_detect |
true |
Enable automatic ESP detection | |
esp.mount_point |
"" |
Manual ESP path (lowest priority) | |
| rEFInd | refind.config_path |
"/EFI/refind/refind.conf" |
Path to main rEFInd config |
| Behavior | behavior.exit_on_snapshot_boot |
true |
Prevent operation when booted from snapshot |
behavior.cleanup_old_snapshots |
true |
Clean up old writable snapshots | |
| Display | display.local_time |
false |
Display times in local time instead of UTC |
| Logging | log_level |
"info" |
Log verbosity: trace, debug, info, warn, error |
| Kernel | kernel.stale_snapshot_action |
"delete" |
Action for stale snapshots: delete, warn, disable, fallback |
kernel.boot_image_patterns |
(built-in) | Custom boot image patterns (see config file) | |
| Advanced | advanced.naming.rwsnap_format |
"2006-01-02_15-04-05" |
Timestamp format for writable snapshot filenames |
advanced.naming.menu_format |
"2006-01-02T15:04:05Z" |
Timestamp format for menu entry titles |
For the full annotated configuration file, see configs/refind-btrfs-snapshots.yaml.
On systems where /boot resides on a separate partition (typically the ESP), kernel images are not captured by btrfs snapshots. After a kernel upgrade, older snapshots may reference kernel modules that no longer exist on disk, resulting in unbootable snapshot entries.
This only applies to ESP-mode snapshots. Btrfs-mode snapshots contain their own kernel and are immune to this problem — see Boot Mode Detection.
During generation, for ESP-mode snapshots:
- Scan: Discovers boot images on the ESP (kernels, initramfs, fallback initramfs, microcode) using configurable glob patterns
- Inspect: Reads kernel binary headers (bzImage format) to extract the exact kernel version string
- Group: Assembles boot images into "boot sets" by kernel name (e.g.,
linux,linux-lts,linux-zen) - Check: For each snapshot, enumerates
/lib/modules/inside the snapshot and compares against the boot set's expected kernel version
The checker uses a three-tier strategy (best available wins):
| Method | Source | Reliability |
|---|---|---|
| Binary header | Reads kernel version from bzImage header, matches against snapshot /lib/modules/<version>/ |
Highest — exact version match |
| Pkgbase | Reads /lib/modules/<version>/pkgbase in the snapshot, matches against boot set kernel name |
High — Arch Linux specific |
| Assumed fresh | Neither method available | Lowest — assumes bootable with a warning |
Configure via kernel.stale_snapshot_action in your config file:
| Action | Behaviour |
|---|---|
delete (default) |
Skips the boot entry entirely — stale snapshots won't appear in the menu |
warn |
Logs a warning, generates the boot entry normally |
disable |
Generates the boot entry with a disabled directive (visible but not bootable) |
fallback |
Uses the fallback initramfs; auto-downgrades to disable if no fallback exists |
Built-in defaults cover most distributions:
- Arch Linux:
vmlinuz-*,initramfs-*.img,initramfs-*-fallback.img - Debian/Ubuntu:
vmlinuz-*,initrd.img-* - Generic:
vmlinuz,vmlinuz.efi,bzImage,initrd.img,initrd,initramfs.img - Microcode:
intel-ucode.img,amd-ucode.img - UKI:
*.efi(matched after kernel/initramfs patterns; typically located under<esp>/EFI/Linux/)
For non-standard naming, override patterns in the config file:
kernel:
boot_image_patterns:
- glob: "vmlinuz-*"
role: kernel
strip_prefix: "vmlinuz-"
- glob: "initramfs-*.img"
role: initramfs
strip_prefix: "initramfs-"
strip_suffix: ".img"
- glob: "initramfs-*-fallback.img"
role: fallback_initramfs
strip_prefix: "initramfs-"
strip_suffix: "-fallback.img"Each pattern supports:
glob— filename glob to matchrole— one ofkernel,initramfs,fallback_initramfs,microcode,ukistrip_prefix/strip_suffix— removed from the filename to derive the kernel namekernel_name— explicit override when stripping isn't possible (e.g., genericvmlinuz)
When refind_linux.conf updates aren't suitable (e.g., custom kernel configurations, multiple boot sets), the tool generates a refind-btrfs-snapshots.conf include file. This provides maximum flexibility while keeping your main rEFInd configuration clean.
Note:
refind_linux.confonly supports ESP-mode operation since it relies on rEFInd's auto-detection of kernel paths. If you have btrfs-mode snapshots, use the include file approach (-gflag) which can emit thevolume,loader, andinitrdoverrides that btrfs-mode requires.
# /boot/efi/EFI/refind/refind-btrfs-snapshots.conf
#
# Generated by refind-btrfs-snapshots
menuentry "Arch Linux" {
disabled
icon /EFI/refind/icons/os_arch.png
loader /boot/vmlinuz-linux
initrd /boot/initramfs-linux.img
options "quiet splash rw rootflags=subvol=/@ root=UUID=..."
# ESP-mode snapshot (kernel on ESP):
submenuentry "Arch Linux (2025-01-15T10:00:00Z)" {
options "quiet splash rw rootflags=subvol=/@/.snapshots/42/snapshot root=UUID=..."
}
# Btrfs-mode snapshot (kernel inside snapshot):
submenuentry "Arch Linux (2025-02-14T10:00:00Z)" {
volume ARCH_ROOT
loader /@/.snapshots/73/snapshot/boot/vmlinuz-linux
initrd /@/.snapshots/73/snapshot/boot/initramfs-linux.img
options "quiet splash rw rootflags=subvol=/@/.snapshots/73/snapshot root=UUID=..."
}
}Setup:
Add this line to your refind.conf:
include refind-btrfs-snapshots.conf
Placement tips:
- Place after your main entries for snapshots to appear at bottom
- Place before main entries for snapshots to appear at top
- Use rEFInd's
default_selectionto control which entry boots by default
Enable path-based monitoring to automatically regenerate boot entries when snapshots change:
sudo systemctl enable --now refind-btrfs-snapshots.path- Path Monitoring: The
.pathunit watches/.snapshotsvia inotify - Trigger Throttling: Waits 10 seconds after the last change to avoid excessive regeneration
- Service Execution: Triggers the
.serviceunit which runsgenerate -g -y - Automatic Include: Forces include file generation and auto-approves changes
To monitor additional snapshot locations:
sudo systemctl edit refind-btrfs-snapshots.path[Path]
PathChanged=/run/timeshift/backup/timeshift-btrfs/snapshots
PathChanged=/custom/snapshotssudo systemctl edit refind-btrfs-snapshots.service[Service]
ExecStart=
ExecStart=/usr/bin/refind-btrfs-snapshots --config /etc/custom-config.yaml generate -g -y -n 10snapshot:
search_directories: ["/.snapshots"]
writable_method: "toggle"
selection_count: 0
max_depth: 3
behavior:
cleanup_old_snapshots: true
display:
local_time: false
advanced:
naming:
menu_format: "2006-01-02T15:04:05Z"snapshot:
search_directories:
- "/run/timeshift/backup/timeshift-btrfs/snapshots"
writable_method: "copy"
selection_count: 5
destination_dir: "/timeshift-rwsnaps"
esp:
mount_point: "/boot/efi"
display:
local_time: true
advanced:
naming:
menu_format: "btrfs snapshot: YYYY/MM/DD-HH:mm"Remember to configure the systemd path unit to monitor Timeshift's snapshot directory.
snapshot:
search_directories:
- "/snapshots/system"
- "/snapshots/home"
writable_method: "copy"
selection_count: 3
destination_dir: "/boot-snapshots"
behavior:
cleanup_old_snapshots: true
exit_on_snapshot_boot: false
advanced:
naming:
rwsnap_format: "2006-01-02_15-04-05"
menu_format: "snapshot-YYYY-MM-DD_HH-mm"- Snapper: Times in
info.xmlare assumed UTC when no timezone is specified - Display: Shown in UTC (default) or local time via
--local-timeflag or config - Menu entries: Use ISO8601 format by default (
2025-06-14T10:00:02Z)
advanced:
naming:
rwsnap_format: "2006-01-02_15-04-05" # For writable snapshot filenames
menu_format: "2006-01-02T15:04:05Z" # For menu entry titlesTemplate placeholders (for menu_format):
YYYY (4-digit year), YY (2-digit year), MM (month), DD (day), HH (hour), mm (minute), ss (second)
Examples:
menu_format: "2006-01-02T15:04:05Z" # "2025-06-14T17:32:09Z"
menu_format: "Jan 02, 2006 15:04" # "Jun 14, 2025 17:32"
menu_format: "btrfs snapshot: YYYY/MM/DD-HH:mm" # "btrfs snapshot: 2025/06/14-17:32"# Debug ESP detection
sudo refind-btrfs-snapshots generate --dry-run --log-level debug
# Force specific ESP
sudo refind-btrfs-snapshots generate --esp-path /boot/efi# List detected snapshots with debug info
sudo refind-btrfs-snapshots list snapshots --log-level debug
# Check search directories
btrfs subvolume list /If snapshots are marked stale or entries missing after a kernel upgrade, start with status — it shows exactly which snapshots are bootable against the current ESP kernels:
# See which snapshots are stale against the current kernel(s)
sudo refind-btrfs-snapshots status
# Drill down to just the unbootable ones
sudo refind-btrfs-snapshots status --unbootable-only
# Inspect the kernel binaries on the ESP
sudo refind-btrfs-snapshots list bootsets --show-images
# Debug staleness detection during generation
sudo refind-btrfs-snapshots generate --dry-run --log-level debug
# Look for "stale" or "modules" messages in outputConfigure the action:
kernel:
stale_snapshot_action: "delete" # or "warn", "disable", "fallback"The tool requires root access to read btrfs subvolume information and write to the ESP:
sudo refind-btrfs-snapshots generatesudo refind-btrfs-snapshots generate --log-level debug --dry-runShows: ESP detection, snapshot discovery, boot image scanning, kernel version inspection, boot mode detection per snapshot, staleness results, configuration resolution, and entry generation.
refind-btrfs-snapshots writes rEFInd-native configuration. The shared discovery layer (kernel scanning, UKI inspection, microcode parsing, BLS Type #1 entry parsing) is bootloader-agnostic, which lets the repo also ship sibling binaries for other bootloaders.
| Bootloader | Repository binary | Snapshot booting | Notes |
|---|---|---|---|
| rEFInd | refind-btrfs-snapshots |
ESP + btrfs modes | This binary. Treats UKIs as standard EFI loaders for discovery only; see UKI Layout. |
| systemd-boot | bls-btrfs-snapshots |
ESP mode only | Writes spec-conformant BLS Type #1 .conf entries under <esp>/loader/entries/. Released on GitHub. |
| BLS-aware GRUB | bls-btrfs-snapshots |
ESP mode only | Same .conf output; works wherever GRUB_ENABLE_BLSCFG=true. |
| non-BLS GRUB | (none yet) | — | Not currently planned. |
| limine | (none yet) | — | Not currently planned. |
| UKI host | (planned) | — | A uki-btrfs-snapshots binary that clones UKIs per snapshot is on the wishlist; see WISHLIST.md. |
For inspecting what the discovery layer sees on a given host — useful regardless of which generator binary you use — the kernel-spy binary dumps every detected kernel image, initramfs, microcode blob, BLS entry, and UKI without modifying anything.
git clone https://github.com/jmylchreest/refind-btrfs-snapshots.git
cd refind-btrfs-snapshots
go build -o refind-btrfs-snapshots ./cmd/refind-btrfs-snapshots
# With version info
go build -ldflags "-X github.com/jmylchreest/refind-btrfs-snapshots/internal/version.Version=v1.0.0" \
-o refind-btrfs-snapshots ./cmd/refind-btrfs-snapshotsgo test ./... # All tests
go test -race -coverprofile=coverage.out ./... # With coverage
go tool cover -html=coverage.out # View coverage report- Fork the repository
- Create a feature branch:
git checkout -b feature-name - Make changes and add tests
- Ensure tests pass:
go test ./... - Submit a pull request