Compare commits

...

6 Commits

Author SHA1 Message Date
Stefan Strobl
667453596a fix(rollback): cleanup bind mounts on failure
Add cleanup for bind mounts (/dev, /proc, /sys, /run) created
during post-install phase. Without this, failures in post-install
leave bind mounts active, preventing subsequent cleanup of the
main filesystem mounts.

Clean bind mounts first (innermost to outermost), then regular
mounts, then LUKS mapping to ensure proper teardown order.

Decision: Check each bind mount individually rather than relying
on umount -R to handle everything, for better error reporting and
partial cleanup capability.

Context: Bind mounts are only created if post-install phase is
reached, but rollback must handle cleanup from any failure point.
2025-12-24 15:28:05 +01:00
Stefan Strobl
98aebc5f09 fix(postinstall): improve error handling and user feedback
Add three critical improvements to post-install phase:

1. Cleanup trap for bind mounts: Ensure /dev, /proc, /sys, /run
   are unmounted even if chroot script fails. Prevents orphaned
   mounts blocking cleanup.

2. Fallocate fallback: Use dd as fallback if fallocate fails
   (can happen on some filesystems or with insufficient space).
   Includes intelligent size conversion and progress display.

3. Progress messages: Add informative echo statements before
   long-running operations (xbps-reconfigure, grub-install,
   grub-mkconfig) so users know the system is working.

Decision: Use trap EXIT instead of manual cleanup to guarantee
execution on both success and error paths. Remove redundant
explicit cleanup call.

Trade-offs: dd fallback is slower but ensures swap file creation
succeeds. Progress messages add noise but significantly improve
UX during multi-minute operations.
2025-12-24 15:27:14 +01:00
Stefan Strobl
551cb98a9d fix(installer): verify mounts after installer completion
Add verification that critical mounts (root and ESP) are still
intact after void-installer exits. Catches cases where installer
accidentally reformats filesystems despite instructions.

Fail fast with clear error message if mounts disappeared, rather
than proceeding to post-install and encountering cryptic errors.

Decision: Check mounts immediately after installer rather than
during post-install to provide clear failure point and message.

Alternative considered: Monitor installer process to prevent
reformatting, rejected as too invasive and complex.
2025-12-24 15:25:42 +01:00
Stefan Strobl
c303a75192 fix(filesystems): add cleanup trap for temp btrfs mount
Add trap to ensure temp mount is cleaned up if btrfs subvolume
creation fails. Without this, failures leave mounted filesystems
and orphaned directories, blocking retry attempts.

Use mountpoint -q for robust mount detection before cleanup.

Decision: Use local trap within format_filesystems to avoid
interfering with main error handler. Reset trap after successful
completion to prevent double cleanup.

Trade-off: Slightly more complex code vs guaranteed cleanup on
error paths.
2025-12-24 15:24:29 +01:00
Stefan Strobl
c9fbc5486c fix(config): add size validation and improve swap normalization
Add validate_size() function to check format of user-provided
size inputs (ESP_SIZE, ROOT_END, SWAP_SIZE) before they reach
partitioning tools. Prevents cryptic parted errors from invalid
formats like "abc" or "1X".

Improve SWAP_SIZE normalization to handle all zero variants
(0, 0G, 0GB, 0GiB, 0M, 0MB, 0MiB) consistently.

Decision: Validate at config phase rather than partition phase
to provide early, clear feedback. Use regex pattern that matches
parted's expected format (number + optional unit or percentage).

Alternative considered: Let parted handle validation, rejected
because error messages would be less user-friendly.
2025-12-24 15:22:50 +01:00
Stefan Strobl
4e0d9573e6 fix(partitioning): export partition variables for module access
Export ESP_PART and ROOT_PART variables to ensure they are
available in all subsequent modules (encryption, filesystems,
postinstall). While sourcing makes them accessible, explicit
exports clarify the sharing model and prevent potential issues
in different shell contexts.

Decision: Use explicit export instead of relying on implicit
variable propagation through source to make dependencies clear.
2025-12-24 15:22:10 +01:00
6 changed files with 84 additions and 3 deletions

View File

@ -53,6 +53,15 @@ export ESP_MOUNT="/mnt/boot/efi"
export ROOT_LABEL="void-root"
export EFI_LABEL="EFI"
validate_size() {
local size="$1"
# Allow sizes like: 1GiB, 100MB, 50%, 100%
if [[ "$size" =~ ^[0-9]+(\.[0-9]+)?(GiB|GB|MiB|MB|%)?$ ]]; then
return 0
fi
return 1
}
prompt_with_default() {
local var_name="$1"
local prompt_text="$2"
@ -145,9 +154,21 @@ config_validate() {
*) die "Unsupported LUKS version: $LUKS_VERSION" ;;
esac
if [[ "$SWAP_SIZE" == "0" || "$SWAP_SIZE" == "0G" || "$SWAP_SIZE" == "0GiB" ]]; then
# Normalize swap size first
if [[ "$SWAP_SIZE" =~ ^0(G|GB|GiB|M|MB|MiB)?$ ]]; then
SWAP_SIZE="0"
fi
# Validate size formats
if ! validate_size "$ESP_SIZE"; then
die "Invalid ESP size format: $ESP_SIZE (expected: 1GiB, 100MB, etc.)"
fi
if ! validate_size "$ROOT_END"; then
die "Invalid root partition end format: $ROOT_END (expected: 100%, 200GiB, etc.)"
fi
if [[ "$SWAP_SIZE" != "0" ]] && ! validate_size "$SWAP_SIZE"; then
die "Invalid swap size format: $SWAP_SIZE (expected: 4GiB, 8GB, 0 to disable)"
fi
}
config_print_summary() {

View File

@ -64,6 +64,18 @@ format_filesystems() {
local temp_mount
temp_mount="/tmp/void-wrapper-btrfs"
mkdir -p "$temp_mount"
# Ensure cleanup on error
cleanup_temp_mount() {
if mountpoint -q "$temp_mount" 2>/dev/null; then
umount "$temp_mount" || log_warn "Failed to unmount temp mount: $temp_mount"
fi
if [[ -d "$temp_mount" ]]; then
rmdir "$temp_mount" 2>/dev/null || true
fi
}
trap cleanup_temp_mount EXIT
mount "$root_device" "$temp_mount"
btrfs subvolume create "$temp_mount/@"
btrfs subvolume create "$temp_mount/@home"
@ -73,6 +85,7 @@ format_filesystems() {
btrfs subvolume create "$temp_mount/@swap"
umount "$temp_mount"
rmdir "$temp_mount"
trap - EXIT
else
log_info "Formatting root as ext4 on $root_device"
mkfs.ext4 -L "$ROOT_LABEL" "$root_device"

View File

@ -58,4 +58,14 @@ run_installer() {
fi
read -r -p "Press Enter once the installer is complete..." _
# Verify critical mounts are still intact after installer
if ! findmnt "$MOUNT_ROOT" >/dev/null 2>&1; then
die "Root mount disappeared after installer. The installer may have reformatted the filesystem."
fi
if ! findmnt "$ESP_MOUNT" >/dev/null 2>&1; then
die "ESP mount disappeared after installer. The installer may have reformatted the filesystem."
fi
log_info "Mount verification passed."
}

View File

@ -83,6 +83,7 @@ partition_disk() {
ESP_PART="$(partition_path "$DISK" 1)"
ROOT_PART="$(partition_path "$DISK" 2)"
export ESP_PART ROOT_PART
log_info "ESP partition: $ESP_PART"
log_info "Root partition: $ROOT_PART"

View File

@ -78,6 +78,7 @@ postinstall_run() {
log_info "Post-install: configuring target system in chroot"
postinstall_bind_mounts
trap postinstall_unbind_mounts EXIT
LUKS_UUID="$luks_uuid" ROOT_UUID="$root_uuid" FS_TYPE="$FS_TYPE" SWAP_SIZE="$SWAP_SIZE" HOSTNAME="$HOSTNAME" \
chroot "$MOUNT_ROOT" /bin/bash -s <<'EOS'
@ -117,7 +118,26 @@ if [[ "$SWAP_SIZE" != "0" ]]; then
btrfs property set /swap compression none || true
fi
fi
fallocate -l "$SWAP_SIZE" /swap/swapfile
# Try fallocate first, fall back to dd if it fails
if ! fallocate -l "$SWAP_SIZE" /swap/swapfile 2>/dev/null; then
echo "fallocate failed, using dd as fallback..."
# Extract numeric size and unit for dd
local size_num="${SWAP_SIZE%%[^0-9.]*}"
local size_unit="${SWAP_SIZE##*[0-9]}"
local bs="1M"
local count="$size_num"
# Convert to MB count for dd
case "$size_unit" in
GiB|G|GB) count=$(awk "BEGIN {print int($size_num * 1024)}") ;;
MiB|M|MB) count=$(awk "BEGIN {print int($size_num)}") ;;
*) echo "Warning: unknown size unit, assuming MiB"; count="$size_num" ;;
esac
dd if=/dev/zero of=/swap/swapfile bs="$bs" count="$count" status=progress
fi
chmod 600 /swap/swapfile
mkswap /swap/swapfile
if ! grep -q '^/swap/swapfile' /etc/fstab; then
@ -168,12 +188,15 @@ else
fi
# Regenerate initramfs and GRUB configuration.
echo "Regenerating initramfs for all kernels (this may take a while)..."
xbps-reconfigure -fa
echo "Installing GRUB bootloader..."
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Void --recheck
echo "Generating GRUB configuration..."
grub-mkconfig -o /boot/grub/grub.cfg
EOS
postinstall_unbind_mounts
log_info "Post-install complete."
}

View File

@ -58,11 +58,24 @@ rollback_offer() {
rollback_cleanup() {
: "${MOUNT_ROOT:?Mount root is required}"
: "${CRYPT_NAME:?Crypt mapping name is required}"
# Clean up bind mounts first (from postinstall)
if [[ -d "$MOUNT_ROOT" ]]; then
for bind_path in dev proc sys run; do
if findmnt "$MOUNT_ROOT/$bind_path" >/dev/null 2>&1; then
log_info "Unmounting bind mount: $MOUNT_ROOT/$bind_path"
umount -R "$MOUNT_ROOT/$bind_path" 2>/dev/null || log_warn "Failed to unmount $MOUNT_ROOT/$bind_path"
fi
done
fi
# Clean up regular mounts
if findmnt "$MOUNT_ROOT" >/dev/null 2>&1; then
log_info "Unmounting $MOUNT_ROOT"
umount -R "$MOUNT_ROOT" || log_warn "Failed to unmount $MOUNT_ROOT"
fi
# Close LUKS mapping
if [[ -e "/dev/mapper/$CRYPT_NAME" ]]; then
log_info "Closing LUKS mapping $CRYPT_NAME"
cryptsetup close "$CRYPT_NAME" || log_warn "Failed to close LUKS mapping"