Fix snapper create-config race: retry the whole dance, not just umount
All checks were successful
Mirror to GitHub / mirror (push) Successful in 3s
Build and publish package / package (push) Successful in 2m5s
Build and release ISO / release-iso (push) Successful in 13m59s

Confirmed on the test laptop's real install: /etc/snapper/configs/ was
completely empty post-install — snapper create-config failed silently and
BOS's advertised snapshot/rollback feature was entirely non-functional,
despite snapper-cleanup.timer being enabled and grub-btrfsd active (both
harmless no-ops with no config to act on).

Root cause is the known chroot-specific busy-mount race already documented
in this section's comments, but the existing recovery (retry umount 5x,
then one lazy-unmount fallback) wasn't sufficient on this hardware — a
lazy unmount detaches the mountpoint from the namespace immediately, but
whatever was holding it busy can take a moment longer to actually release,
and the immediately-following rmdir/create-config both fail if anything
still references /.snapshots at that instant.

Wrap the entire unmount → rmdir → create-config → cleanup → remount
sequence in an outer retry loop (checking whether the config file actually
exists before each attempt and after the loop), add a settle delay after
the lazy-unmount fallback, and turn the final failure into a loud ERROR
instead of a warning that's easy to miss — a system silently shipping
without snapshots is worse than one that's slow to set them up.
This commit is contained in:
Breadway 2026-07-04 19:00:15 +08:00
parent 22a8db09a6
commit 434efcea22

View file

@ -272,30 +272,48 @@ fi
# transiently fail inside the Calamares chroot (the same mount unmounts
# cleanly moments later once booted normally — a chroot-specific busy-mount
# race, not a logic error), which cascades into snapper create-config
# refusing because .snapshots "already exists". Retry a few times, then
# fall back to a lazy unmount (detaches the mountpoint immediately even if
# something still transiently references it) rather than give up.
# refusing because .snapshots "already exists". A single pass — retry the
# umount a few times, then fall back to a lazy unmount — was NOT enough on
# real hardware: also confirmed is a run where every umount attempt in that
# single pass failed (including the lazy fallback settling too slowly for
# the immediately-following rmdir/create-config), leaving
# /etc/snapper/configs/ completely empty and BOS's advertised snapshot/
# rollback feature silently non-functional on that install. So retry the
# WHOLE dance, not just the umount substep, and verify at the end that the
# config file actually exists before declaring success.
# ---------------------------------------------------------------------------
if command -v snapper &>/dev/null; then
unmounted=0
for _ in 1 2 3 4 5; do
if umount /.snapshots 2>/dev/null; then
unmounted=1
break
for attempt in 1 2 3; do
[[ -f /etc/snapper/configs/root ]] && break
unmounted=0
for _ in 1 2 3 4 5; do
if umount /.snapshots 2>/dev/null; then
unmounted=1
break
fi
sleep 1
done
if [[ "$unmounted" != "1" ]]; then
echo "WARN: umount /.snapshots failed after retries (attempt $attempt), forcing lazy unmount"
umount -l /.snapshots || echo "WARN: lazy umount /.snapshots also failed (attempt $attempt)"
# Lazy unmount detaches the mountpoint from the namespace right
# away, but whatever was holding it busy may take a moment
# longer to actually let go — rmdir/create-config right after
# this both fail if anything still references /.snapshots.
sleep 2
fi
sleep 1
rmdir /.snapshots 2>/dev/null || echo "WARN: rmdir /.snapshots failed (attempt $attempt)"
snapper -c root create-config / || echo "WARN: snapper create-config failed (attempt $attempt)"
if [[ -d /.snapshots ]]; then
btrfs subvolume delete /.snapshots || echo "WARN: deleting snapper's own .snapshots subvolume failed (attempt $attempt)"
fi
mkdir -p /.snapshots
mount /.snapshots || echo "WARN: remounting the real @snapshots subvolume failed (attempt $attempt)"
[[ -f /etc/snapper/configs/root ]] || sleep 2
done
if [[ "$unmounted" != "1" ]]; then
echo "WARN: umount /.snapshots failed after retries, forcing lazy unmount"
umount -l /.snapshots || echo "WARN: lazy umount /.snapshots also failed"
fi
rmdir /.snapshots || echo "WARN: rmdir /.snapshots failed"
snapper -c root create-config / || echo "WARN: snapper create-config failed"
if [[ -d /.snapshots ]]; then
btrfs subvolume delete /.snapshots || echo "WARN: deleting snapper's own .snapshots subvolume failed"
fi
mkdir -p /.snapshots
mount /.snapshots || echo "WARN: remounting the real @snapshots subvolume failed"
if [[ -f /etc/snapper/configs/root ]]; then
sed -i 's/TIMELINE_CREATE="yes"/TIMELINE_CREATE="no"/' /etc/snapper/configs/root
sed -i 's/NUMBER_CLEANUP="no"/NUMBER_CLEANUP="yes"/' /etc/snapper/configs/root
@ -304,6 +322,8 @@ if command -v snapper &>/dev/null; then
sed -i 's/NUMBER_LIMIT_IMPORTANT="[^"]*"/NUMBER_LIMIT_IMPORTANT="5"/' /etc/snapper/configs/root
[[ -n "$MAIN_USER" ]] && \
sed -i "s/ALLOW_USERS=\"\"/ALLOW_USERS=\"$MAIN_USER\"/" /etc/snapper/configs/root
else
echo "ERROR: snapper config for root still missing after 3 attempts — snapshots/rollback will not work on this install"
fi
fi