source "$GENTOO_INSTALL_REPO_DIR/scripts/protection.sh" || exit 1 ################################################ # Script internal configuration # The temporary directory for this script, # must reside in /tmp to allow the chrooted system to access the files TMP_DIR="/tmp/gentoo-install" # Mountpoint for the new system ROOT_MOUNTPOINT="$TMP_DIR/root" # Mountpoint for the script files for access from chroot GENTOO_INSTALL_REPO_BIND="$TMP_DIR/bind" # Mountpoint for the script files for access from chroot UUID_STORAGE_DIR="$TMP_DIR/uuids" # Backup dir for luks headers LUKS_HEADER_BACKUP_DIR="$TMP_DIR/luks-headers" # Flag to track usage of raid (needed to check for mdadm existence) USED_RAID=false # Flag to track usage of luks (needed to check for cryptsetup existence) USED_LUKS=false # Flag to track usage of btrfs USED_BTRFS=false # An array of disk related actions to perform DISK_ACTIONS=() # An array of dracut parameters needed to boot the selected configuration DISK_DRACUT_CMDLINE=("rd.vconsole.keymap=$KEYMAP_INITRAMFS") # An associative array from disk id to a resolvable string declare -A DISK_ID_TO_RESOLVABLE # An associative array from disk id to parent gpt disk id (only for partitions) declare -A DISK_ID_PART_TO_GPT_ID # An associative array to check for existing ids (maps to uuids) declare -A DISK_ID_TO_UUID # An associative set to check for correct usage of size=remaining in gpt tables declare -A DISK_GPT_HAD_SIZE_REMAINING only_one_of() { local previous="" local a for a in "$@"; do if [[ -v arguments[$a] ]]; then if [[ -z $previous ]]; then previous="$a" else die_trace 2 "Only one of the arguments ($*) can be given" fi fi done } create_new_id() { local id="${arguments[$1]}" [[ $id == *';'* ]] \ && die_trace 2 "Identifier contains invalid character ';'" [[ ! -v DISK_ID_TO_UUID[$id] ]] \ || die_trace 2 "Identifier '$id' already exists" DISK_ID_TO_UUID[$id]="$(load_or_generate_uuid "$(base64 -w 0 <<< "$id")")" } verify_existing_id() { local id="${arguments[$1]}" [[ -v DISK_ID_TO_UUID[$id] ]] \ || die_trace 2 "Identifier $1='$id' not found" } verify_existing_unique_ids() { local arg="$1" local ids="${arguments[$arg]}" count_orig="$(tr ';' '\n' <<< "$ids" | grep -c '\S')" count_uniq="$(tr ';' '\n' <<< "$ids" | grep '\S' | sort -u | wc -l)" [[ $count_orig -gt 0 ]] \ || die_trace 2 "$arg=... must contain at least one entry" [[ $count_orig -eq $count_uniq ]] \ || die_trace 2 "$arg=... contains duplicate identifiers" local id # Splitting is intentional here # shellcheck disable=SC2086 for id in ${ids//';'/ }; do [[ -v DISK_ID_TO_UUID[$id] ]] \ || die_trace 2 "$arg=... contains unknown identifier '$id'" done } verify_option() { local opt="$1" shift local arg="${arguments[$opt]}" local i for i in "$@"; do [[ $i == "$arg" ]] \ && return 0 done die_trace 2 "Invalid option $opt='$arg', must be one of ($*)" } # Named arguments: # new_id: Id for the new gpt table # device|id: The operand block device or previously allocated id create_gpt() { local known_arguments=('+new_id' '+device|id') local extra_arguments=() declare -A arguments; parse_arguments "$@" only_one_of device id create_new_id new_id [[ -v arguments[id] ]] \ && verify_existing_id id local new_id="${arguments[new_id]}" create_resolve_entry "$new_id" ptuuid "${DISK_ID_TO_UUID[$new_id]}" DISK_ACTIONS+=("action=create_gpt" "$@" ";") } # Named arguments: # new_id: Id for the new partition # size: Size for the new partition, or 'remaining' to allocate the rest # type: The parition type, either (bios, efi, swap, raid, luks, linux) (or a 4 digit hex-code for gdisk). # id: The operand device id create_partition() { local known_arguments=('+new_id' '+id' '+size' '+type') local extra_arguments=() declare -A arguments; parse_arguments "$@" create_new_id new_id verify_existing_id id verify_option type bios efi swap raid luks linux [[ -v "DISK_GPT_HAD_SIZE_REMAINING[${arguments[id]}]" ]] \ && die_trace 1 "Cannot add another partition to table (${arguments[id]}) after size=remaining was used" [[ ${arguments[size]} == "remaining" ]] \ && DISK_GPT_HAD_SIZE_REMAINING[${arguments[id]}]=true local new_id="${arguments[new_id]}" DISK_ID_PART_TO_GPT_ID[$new_id]="${arguments[id]}" create_resolve_entry "$new_id" partuuid "${DISK_ID_TO_UUID[$new_id]}" DISK_ACTIONS+=("action=create_partition" "$@" ";") } # Named arguments: # new_id: Id for the new raid # level: Raid level # name: Raid name (/dev/md/) # ids: Comma separated list of all member ids create_raid() { USED_RAID=true local known_arguments=('+new_id' '+level' '+name' '+ids') local extra_arguments=() declare -A arguments; parse_arguments "$@" create_new_id new_id verify_option level 0 1 5 6 verify_existing_unique_ids ids local new_id="${arguments[new_id]}" local uuid="${DISK_ID_TO_UUID[$new_id]}" create_resolve_entry "$new_id" mdadm "$uuid" DISK_DRACUT_CMDLINE+=("rd.md.uuid=$(uuid_to_mduuid "$uuid")") DISK_ACTIONS+=("action=create_raid" "$@" ";") } # Named arguments: # new_id: Id for the new luks # id: The operand device id create_luks() { USED_LUKS=true local known_arguments=('+new_id' '+name' '+device|id') local extra_arguments=() declare -A arguments; parse_arguments "$@" only_one_of device id create_new_id new_id [[ -v arguments[id] ]] \ && verify_existing_id id local new_id="${arguments[new_id]}" local name="${arguments[name]}" local uuid="${DISK_ID_TO_UUID[$new_id]}" create_resolve_entry "$new_id" luks "$name" DISK_DRACUT_CMDLINE+=("rd.luks.uuid=$uuid") DISK_ACTIONS+=("action=create_luks" "$@" ";") } # Named arguments: # new_id: Id for the new luks # device: The device create_dummy() { local known_arguments=('+new_id' '+device') local extra_arguments=() declare -A arguments; parse_arguments "$@" create_new_id new_id local new_id="${arguments[new_id]}" local device="${arguments[device]}" local uuid="${DISK_ID_TO_UUID[$new_id]}" create_resolve_entry_device "$new_id" "$device" DISK_ACTIONS+=("action=create_dummy" "$@" ";") } # Named arguments: # id: Id of the device / partition created earlier # type: One of (bios, efi, swap, ext4) # label: The label for the formatted disk format() { local known_arguments=('+id' '+type' '?label') local extra_arguments=() declare -A arguments; parse_arguments "$@" verify_existing_id id verify_option type bios efi swap ext4 btrfs local type="${arguments[type]}" if [[ "$type" == "btrfs" ]]; then USED_BTRFS=true fi DISK_ACTIONS+=("action=format" "$@" ";") } # Named arguments: # ids: List of ids for devices / partitions created earlier. Must contain at least 1 element. # label: The label for the formatted disk format_btrfs() { USED_BTRFS=true local known_arguments=('+ids' '?raid_type' '?label') local extra_arguments=() declare -A arguments; parse_arguments "$@" verify_existing_unique_ids ids DISK_ACTIONS+=("action=format_btrfs" "$@" ";") } # Returns a comma separated list of all registered ids matching the given regex. expand_ids() { local regex="$1" for id in "${!DISK_ID_TO_UUID[@]}"; do [[ $id =~ $regex ]] \ && echo -n "$id;" done } # Example 1: Single disk, 3 partitions (efi, swap, root) # Parameters: # swap= Create a swap partition with given size, or no swap at all if set to false # type=[efi|bios] Selects the boot type. Defaults to efi if not given. # luks=[true|false] Encrypt root partition. Defaults to false if not given. # root_fs=[ext4|btrfs] Root filesystem create_single_disk_layout() { local known_arguments=('+swap' '?type' '?luks' '?root_fs') local extra_arguments=() declare -A arguments; parse_arguments "$@" [[ ${#extra_arguments[@]} -eq 1 ]] \ || die_trace 1 "Expected exactly one positional argument (the device)" local device="${extra_arguments[0]}" local size_swap="${arguments[swap]}" local type="${arguments[type]}" local use_luks="${arguments[luks]:-false}" local root_fs="${arguments[root_fs]:-ext4}" local efi=true case "$type" in 'bios') efi=false type=bios ;; 'efi'|'') efi=true type=efi ;; *) die_trace 1 "Invalid argument type=$type, must be one of (bios, efi)" ;; esac create_gpt new_id=gpt device="$device" create_partition new_id="part_$type" id=gpt size=256MiB type="$type" [[ $size_swap != "false" ]] && \ create_partition new_id=part_swap id=gpt size="$size_swap" type=swap create_partition new_id=part_root id=gpt size=remaining type=linux local root_id="part_root" if [[ "$use_luks" == "true" ]]; then create_luks new_id=part_luks_root name="root" id=part_root root_id="part_luks_root" fi format id="part_$type" type="$type" label="$type" [[ $size_swap != "false" ]] && \ format id=part_swap type=swap label=swap format id="$root_id" type="$root_fs" label=root if [[ $type == "efi" ]]; then DISK_ID_EFI="part_$type" else DISK_ID_BIOS="part_$type" fi [[ $size_swap != "false" ]] && \ DISK_ID_SWAP=part_swap DISK_ID_ROOT="$root_id" if [[ $root_fs == "btrfs" ]]; then DISK_ID_ROOT_TYPE="btrfs" DISK_ID_ROOT_MOUNT_OPTS="defaults,noatime,compress-force=zstd,subvol=/root" elif [[ $root_fs == "ext4" ]]; then DISK_ID_ROOT_TYPE="ext4" DISK_ID_ROOT_MOUNT_OPTS="defaults,noatime,errors=remount-ro,discard" else die "Unsupported root filesystem type" fi } # Example 2: Multiple disks, with raid 0 and luks # - efi: partition on all disks, but only first disk used # - swap: raid 0 → fs # - root: raid 0 → luks → fs # Parameters: # swap= Create a swap partition with given size for each disk, or no swap at all if set to false # type=[efi|bios] Selects the boot type. Defaults to efi if not given. # root_fs=[ext4|btrfs] Root filesystem create_raid0_luks_layout() { local known_arguments=('+swap' '?type' '?root_fs') local extra_arguments=() declare -A arguments; parse_arguments "$@" [[ ${#extra_arguments[@]} -gt 0 ]] \ || die_trace 1 "Expected at least one positional argument (the devices)" local size_swap="${arguments[swap]}" local type="${arguments[type]}" local root_fs="${arguments[root_fs]:-ext4}" local efi=true case "$type" in 'bios') efi=false type=bios ;; 'efi'|'') efi=true type=efi ;; *) die_trace 1 "Invalid argument type=$type, must be one of (bios, efi)" ;; esac for i in "${!extra_arguments[@]}"; do create_gpt new_id="gpt_dev${i}" device="${extra_arguments[$i]}" create_partition new_id="part_${type}_dev${i}" id="gpt_dev${i}" size=256MiB type="$type" [[ $size_swap != "false" ]] && \ create_partition new_id="part_swap_dev${i}" id="gpt_dev${i}" size="$size_swap" type=raid create_partition new_id="part_root_dev${i}" id="gpt_dev${i}" size=remaining type=raid done [[ $size_swap != "false" ]] && \ create_raid new_id=part_raid_swap name="swap" level=0 ids="$(expand_ids '^part_swap_dev[[:digit:]]$')" create_raid new_id=part_raid_root name="root" level=0 ids="$(expand_ids '^part_root_dev[[:digit:]]$')" create_luks new_id=part_luks_root name="root" id=part_raid_root format id="part_${type}_dev0" type="$type" label="$type" [[ $size_swap != "false" ]] && \ format id=part_raid_swap type=swap label=swap format id=part_luks_root type="$root_fs" label=root if [[ $type == "efi" ]]; then DISK_ID_EFI="part_${type}_dev0" else DISK_ID_BIOS="part_${type}_dev0" fi [[ $size_swap != "false" ]] && \ DISK_ID_SWAP=part_raid_swap DISK_ID_ROOT=part_luks_root if [[ $root_fs == "btrfs" ]]; then DISK_ID_ROOT_TYPE="btrfs" DISK_ID_ROOT_MOUNT_OPTS="defaults,noatime,compress=zstd,subvol=/root" elif [[ $root_fs == "btrfs" ]]; then DISK_ID_ROOT_TYPE="ext4" DISK_ID_ROOT_MOUNT_OPTS="defaults,noatime,errors=remount-ro,discard" else die "Unsupported root filesystem type" fi } # Example 3: Multiple disks, up to 3 partitions on first disk (efi, maybe swap, dm-crypt for btrfs). # Additional devices will be first encrypted and then put directly into btrfs array. # Parameters: # swap= Create a swap partition with given size, or no swap at all if set to false # type=[efi|bios] Selects the boot type. Defaults to efi if not given. # luks=[true|false] Encrypt root partition and btrfs devices. Defaults to false if not given. # raid_type=[raid0|raid1] Select raid type. Defaults to raid0. create_btrfs_raid_layout() { local known_arguments=('+swap' '?type' '?raid_type' '?luks') local extra_arguments=() declare -A arguments; parse_arguments "$@" [[ ${#extra_arguments[@]} -gt 0 ]] \ || die_trace 1 "Expected at least one positional argument (the devices)" local device="${extra_arguments[0]}" local size_swap="${arguments[swap]}" local raid_type="${arguments[raid_type]:-raid0}" local type="${arguments[type]}" local use_luks="${arguments[luks]:-false}" local efi=true case "$type" in 'bios') efi=false type=bios ;; 'efi'|'') efi=true type=efi ;; *) die_trace 1 "Invalid argument type=$type, must be one of (bios, efi)" ;; esac # Create layout on first disk create_gpt new_id="gpt_dev0" device="${extra_arguments[0]}" create_partition new_id="part_${type}_dev0" id="gpt_dev0" size=256MiB type="$type" [[ $size_swap != "false" ]] && \ create_partition new_id="part_swap_dev0" id="gpt_dev0" size="$size_swap" type=swap create_partition new_id="part_root_dev0" id="gpt_dev0" size=remaining type=linux local root_id local root_ids="" if [[ "$use_luks" == "true" ]]; then create_luks new_id=luks_dev0 name="luks_root_0" id=part_root_dev0 root_id="luks_dev0" root_ids="${root_ids}luks_dev0;" for i in "${!extra_arguments[@]}"; do [[ $i != 0 ]] || continue create_luks new_id="luks_dev$i" name="luks_root_$i" device="${extra_arguments[$i]}" root_ids="${root_ids}luks_dev$i;" done else local dev_id="" root_id="part_root_dev0" root_ids="${root_ids}part_root_dev0;" for i in "${!extra_arguments[@]}"; do [[ $i != 0 ]] || continue dev_id="root_dev$i" create_dummy new_id="$dev_id" device="${extra_arguments[$i]}" root_ids="${root_ids}$dev_id;" done fi format id="part_${type}_dev0" type="$type" label="$type" [[ $size_swap != "false" ]] && \ format id="part_swap_dev0" type=swap label=swap format_btrfs ids="$root_ids" label=root raid_type="$raid_type" if [[ $type == "efi" ]]; then DISK_ID_EFI="part_${type}_dev0" else DISK_ID_BIOS="part_${type}_dev0" fi [[ $size_swap != "false" ]] && \ DISK_ID_SWAP=part_swap_dev0 DISK_ID_ROOT="$root_id" DISK_ID_ROOT_TYPE="btrfs" DISK_ID_ROOT_MOUNT_OPTS="defaults,noatime,compress=zstd,subvol=/root" }