void-bootstrapp/src/postinstall.sh
2025-12-25 10:31:30 +01:00

220 lines
8.0 KiB
Bash

#!/usr/bin/env bash
# === Motivation ===
# Ensure the system can boot with encrypted root after installation.
# === Problem Statement ===
# Post-install steps must align initramfs, bootloader, and encryption metadata.
# === Scope ===
# In scope: required configuration updates inside the installed system.
# Out of scope: package selection and user account management.
# === Concepts ===
# Initramfs: early boot image that unlocks encrypted storage.
# Bootloader config: entries that point to the encrypted root.
# Dracut modules: crypt (LUKS unlock), resume (hibernation support if swap file used).
# Crypttab: /etc/crypttab maps LUKS UUIDs to device names for initramfs.
# GRUB cryptodisk: GRUB_ENABLE_CRYPTODISK=y in /etc/default/grub enables LUKS unlock.
# Kernel parameters: rd.luks.uuid=<UUID> tells dracut which LUKS container to unlock.
# === Decisions ===
# Keep post-install steps explicit and minimal, focused on boot viability.
# Default to GRUB because it is widely supported on Void without systemd dependencies.
# Regenerate initramfs for all installed kernels to avoid boot drift.
# Use UUID-based references in /etc/crypttab for stability across device naming changes.
# Enable dracut crypt module explicitly in /etc/dracut.conf.d/10-crypt.conf.
# Set GRUB_ENABLE_CRYPTODISK=y in /etc/default/grub to allow GRUB to unlock LUKS.
# Add rd.luks.uuid=<UUID> to GRUB_CMDLINE_LINUX for initramfs LUKS unlock.
# Run grub-install to embed cryptodisk support, then grub-mkconfig to update menu entries.
# === Alternatives Considered ===
# Skipping post-install updates rejected because it risks unbootable systems.
# systemd-boot and rEFInd rejected as defaults due to availability and scope constraints.
# EFISTUB rejected as default because it increases manual UEFI entry management.
# === Constraints ===
# Steps must run in the target system context.
# === Open Questions ===
# Should we generate a rescue initramfs in addition to the default one?
# Should we verify GRUB can unlock LUKS2 before rebooting, or trust the configuration?
# How do we handle future kernel updates - should we document the dracut reconfiguration process?
# Should swap file activation be configured in this phase, or deferred to first boot?
# === Success Criteria ===
# After reboot, the system prompts for decryption and boots successfully.
postinstall_bind_mounts() {
mkdir -p "$MOUNT_ROOT/dev" "$MOUNT_ROOT/proc" "$MOUNT_ROOT/sys" "$MOUNT_ROOT/run"
mount --rbind /dev "$MOUNT_ROOT/dev"
mount --make-rslave "$MOUNT_ROOT/dev"
mount -t proc /proc "$MOUNT_ROOT/proc"
mount --rbind /sys "$MOUNT_ROOT/sys"
mount --make-rslave "$MOUNT_ROOT/sys"
mount --rbind /run "$MOUNT_ROOT/run"
mount --make-rslave "$MOUNT_ROOT/run"
}
postinstall_unbind_mounts() {
umount -R "$MOUNT_ROOT/dev" 2>/dev/null || true
umount -R "$MOUNT_ROOT/proc" 2>/dev/null || true
umount -R "$MOUNT_ROOT/sys" 2>/dev/null || true
umount -R "$MOUNT_ROOT/run" 2>/dev/null || true
}
postinstall_run() {
: "${ROOT_PART:?Root partition is required}"
: "${ESP_PART:?ESP partition is required}"
: "${CRYPT_NAME:?Crypt mapping name is required}"
: "${MOUNT_ROOT:?Mount root is required}"
: "${FS_TYPE:?Filesystem type is required}"
: "${SWAP_SIZE:?Swap size is required}"
: "${HOSTNAME:?Hostname is required}"
local luks_uuid
local root_uuid
local esp_uuid
luks_uuid="$(cryptsetup luksUUID "$ROOT_PART")"
root_uuid="$(blkid -s UUID -o value "/dev/mapper/${CRYPT_NAME}")"
esp_uuid="$(blkid -s UUID -o value "$ESP_PART")"
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" ESP_UUID="$esp_uuid" FS_TYPE="$FS_TYPE" SWAP_SIZE="$SWAP_SIZE" HOSTNAME="$HOSTNAME" \
chroot "$MOUNT_ROOT" /bin/bash -s <<'EOS'
set -euo pipefail
# Ensure required tools are available inside the target system.
for cmd in grub-install grub-mkconfig xbps-reconfigure fallocate mkswap; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "Missing required command inside chroot: $cmd" >&2
exit 1
fi
done
# Write the hostname explicitly to match wrapper configuration.
printf '%s\n' "$HOSTNAME" > /etc/hostname
# Ensure crypttab exists with the correct UUID mapping.
mkdir -p /etc
printf 'cryptroot UUID=%s none luks\n' "$LUKS_UUID" > /etc/crypttab
# Ensure dracut loads crypt and optional resume modules.
mkdir -p /etc/dracut.conf.d
if [[ "$SWAP_SIZE" != "0" ]]; then
printf 'add_dracutmodules+=" crypt resume "\n' > /etc/dracut.conf.d/10-crypt.conf
else
printf 'add_dracutmodules+=" crypt "\n' > /etc/dracut.conf.d/10-crypt.conf
fi
update_fstab_entry() {
local mount_point="$1"
local fs_type="$2"
local options="$3"
local uuid="$4"
if [[ -z "$uuid" ]]; then
echo "Missing UUID for $mount_point" >&2
exit 1
fi
local line="UUID=$uuid $mount_point $fs_type $options 0 0"
if awk -v mp="$mount_point" '$2==mp {found=1} END {exit found?0:1}' /etc/fstab; then
awk -v mp="$mount_point" -v line="$line" 'BEGIN{OFS=" "} $2==mp {$0=line} {print}' /etc/fstab > /etc/fstab.tmp
mv /etc/fstab.tmp /etc/fstab
else
printf '%s\n' "$line" >> /etc/fstab
fi
}
create_swapfile() {
local swap_size="$1"
# 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..."
local size_num
local size_unit
local bs="1M"
local count
size_num="${swap_size%%[^0-9.]*}"
size_unit="${swap_size##*[0-9.]}"
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=$(awk "BEGIN {print int($size_num)}") ;;
esac
dd if=/dev/zero of=/swap/swapfile bs="$bs" count="$count" status=progress
fi
}
touch /etc/fstab
update_fstab_entry /boot/efi vfat "defaults" "$ESP_UUID"
if [[ "$FS_TYPE" == "btrfs" ]]; then
update_fstab_entry / "btrfs" "defaults,subvol=@" "$ROOT_UUID"
update_fstab_entry /home "btrfs" "defaults,subvol=@home" "$ROOT_UUID"
update_fstab_entry /var "btrfs" "defaults,subvol=@var" "$ROOT_UUID"
update_fstab_entry /var/log "btrfs" "defaults,subvol=@log" "$ROOT_UUID"
update_fstab_entry /.snapshots "btrfs" "defaults,subvol=@snapshots" "$ROOT_UUID"
update_fstab_entry /swap "btrfs" "defaults,subvol=@swap" "$ROOT_UUID"
else
update_fstab_entry / "ext4" "defaults" "$ROOT_UUID"
fi
# Prepare a swap file inside the encrypted root if enabled.
if [[ "$SWAP_SIZE" != "0" ]]; then
mkdir -p /swap
if [[ "$FS_TYPE" == "btrfs" ]]; then
if command -v chattr >/dev/null 2>&1; then
chattr +C /swap
fi
if command -v btrfs >/dev/null 2>&1; then
btrfs property set /swap compression none || true
fi
fi
create_swapfile "$SWAP_SIZE"
chmod 600 /swap/swapfile
mkswap /swap/swapfile
if ! grep -q '^/swap/swapfile' /etc/fstab; then
printf '/swap/swapfile none swap defaults 0 0\n' >> /etc/fstab
fi
fi
# Ensure GRUB unlocks the encrypted root and passes the LUKS UUID to initramfs.
mkdir -p /etc/default
touch /etc/default/grub
if grep -q '^GRUB_ENABLE_CRYPTODISK=' /etc/default/grub; then
sed -i 's/^GRUB_ENABLE_CRYPTODISK=.*/GRUB_ENABLE_CRYPTODISK=y/' /etc/default/grub
else
printf 'GRUB_ENABLE_CRYPTODISK=y\n' >> /etc/default/grub
fi
if grep -q '^GRUB_CMDLINE_LINUX=' /etc/default/grub; then
if ! grep -q "rd.luks.uuid=$LUKS_UUID" /etc/default/grub; then
sed -i "s/^GRUB_CMDLINE_LINUX=\"/GRUB_CMDLINE_LINUX=\"rd.luks.uuid=$LUKS_UUID /" /etc/default/grub
fi
else
printf 'GRUB_CMDLINE_LINUX="rd.luks.uuid=%s"\n' "$LUKS_UUID" >> /etc/default/grub
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
log_info "Post-install complete."
}