Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall/lib/installer.py
diff options
context:
space:
mode:
Diffstat (limited to 'archinstall/lib/installer.py')
-rw-r--r--archinstall/lib/installer.py365
1 files changed, 199 insertions, 166 deletions
diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py
index daac340b..1ead46c7 100644
--- a/archinstall/lib/installer.py
+++ b/archinstall/lib/installer.py
@@ -12,7 +12,7 @@ from .disk import get_partitions_in_use, Partition
from .general import SysCommand, generate_password
from .hardware import has_uefi, is_vm, cpu_vendor
from .locale_helpers import verify_keyboard_layout, verify_x11_keyboard_layout
-from .disk.helpers import get_mount_info, split_bind_name
+from .disk.helpers import get_mount_info
from .mirrors import use_mirrors
from .plugins import plugins
from .storage import storage
@@ -21,7 +21,7 @@ from .output import log
from .profiles import Profile
from .disk.btrfs import manage_btrfs_subvolumes
from .disk.partition import get_mount_fs_type
-from .exceptions import DiskError, ServiceException, RequirementError, HardwareIncompatibilityError
+from .exceptions import DiskError, ServiceException, RequirementError, HardwareIncompatibilityError, SysCallError
# Any package that the Installer() is responsible for (optional and the default ones)
__packages__ = ["base", "base-devel", "linux-firmware", "linux", "linux-lts", "linux-zen", "linux-hardened"]
@@ -165,7 +165,7 @@ class Installer:
@property
def partitions(self) -> List[Partition]:
- return get_partitions_in_use(self.target)
+ return get_partitions_in_use(self.target).values()
def sync_log_to_install_medium(self) -> bool:
# Copy over the install log (if there is one) to the install medium if
@@ -259,12 +259,15 @@ class Installer:
for partition in sorted([entry for entry in list_part if entry.get('mountpoint',False)],key=lambda part: part['mountpoint']):
mountpoint = partition['mountpoint']
log(f"Mounting {mountpoint} to {self.target}{mountpoint} using {partition['device_instance']}", level=logging.INFO)
+
if partition.get('filesystem',{}).get('mount_options',[]):
mount_options = ','.join(partition['filesystem']['mount_options'])
partition['device_instance'].mount(f"{self.target}{mountpoint}",options=mount_options)
else:
partition['device_instance'].mount(f"{self.target}{mountpoint}")
+
time.sleep(1)
+
try:
get_mount_info(f"{self.target}{mountpoint}", traverse=False)
except DiskError:
@@ -501,11 +504,17 @@ class Installer:
return True
def detect_encryption(self, partition :Partition) -> bool:
- part = Partition(partition.parent, None, autodetect_filesystem=True)
- if partition.encrypted:
+ from .disk.mapperdev import MapperDev
+ from .disk.dmcryptdev import DMCryptDev
+ from .disk.helpers import get_filesystem_type
+
+ if type(partition) is MapperDev:
+ # Returns MapperDev.partition
+ return partition.partition
+ elif type(partition) is DMCryptDev:
+ return partition.MapperDev.partition
+ elif get_filesystem_type(partition.path) == 'crypto_LUKS':
return partition
- elif partition.parent not in partition.path and part.filesystem == 'crypto_LUKS':
- return part
return False
@@ -627,6 +636,184 @@ class Installer:
else:
raise ValueError(f"Archinstall currently only supports setting up swap on zram")
+ def add_systemd_bootloader(self, boot_partition :Partition, root_partition :Partition) -> bool:
+ self.pacstrap('efibootmgr')
+
+ if not has_uefi():
+ raise HardwareIncompatibilityError
+ # TODO: Ideally we would want to check if another config
+ # points towards the same disk and/or partition.
+ # And in which case we should do some clean up.
+
+ # Install the boot loader
+ if SysCommand(f'/usr/bin/arch-chroot {self.target} bootctl --path=/boot install').exit_code != 0:
+ # Fallback, try creating the boot loader without touching the EFI variables
+ SysCommand(f'/usr/bin/arch-chroot {self.target} bootctl --no-variables --path=/boot install')
+
+ # Ensure that the /boot/loader directory exists before we try to create files in it
+ if not os.path.exists(f'{self.target}/boot/loader'):
+ os.makedirs(f'{self.target}/boot/loader')
+
+ # Modify or create a loader.conf
+ if os.path.isfile(f'{self.target}/boot/loader/loader.conf'):
+ with open(f'{self.target}/boot/loader/loader.conf', 'r') as loader:
+ loader_data = loader.read().split('\n')
+ else:
+ loader_data = [
+ f"default {self.init_time}",
+ "timeout 5"
+ ]
+
+ with open(f'{self.target}/boot/loader/loader.conf', 'w') as loader:
+ for line in loader_data:
+ if line[:8] == 'default ':
+ loader.write(f'default {self.init_time}_{self.kernels[0]}\n')
+ elif line[:8] == '#timeout' and 'timeout 5' not in loader_data:
+ # We add in the default timeout to support dual-boot
+ loader.write(f"{line[1:]}\n")
+ else:
+ loader.write(f"{line}\n")
+
+ # Ensure that the /boot/loader/entries directory exists before we try to create files in it
+ if not os.path.exists(f'{self.target}/boot/loader/entries'):
+ os.makedirs(f'{self.target}/boot/loader/entries')
+
+ for kernel in self.kernels:
+ # Setup the loader entry
+ with open(f'{self.target}/boot/loader/entries/{self.init_time}_{kernel}.conf', 'w') as entry:
+ entry.write('# Created by: archinstall\n')
+ entry.write(f'# Created on: {self.init_time}\n')
+ entry.write(f'title Arch Linux ({kernel})\n')
+ entry.write(f"linux /vmlinuz-{kernel}\n")
+ if not is_vm():
+ vendor = cpu_vendor()
+ if vendor == "AuthenticAMD":
+ entry.write("initrd /amd-ucode.img\n")
+ elif vendor == "GenuineIntel":
+ entry.write("initrd /intel-ucode.img\n")
+ else:
+ self.log("unknow cpu vendor, not adding ucode to systemd-boot config")
+ entry.write(f"initrd /initramfs-{kernel}.img\n")
+ # blkid doesn't trigger on loopback devices really well,
+ # so we'll use the old manual method until we get that sorted out.
+ root_fs_type = get_mount_fs_type(root_partition.filesystem)
+
+ if root_fs_type is not None:
+ options_entry = f'rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}\n'
+ else:
+ options_entry = f'rw intel_pstate=no_hwp {" ".join(self.KERNEL_PARAMS)}\n'
+
+ for subvolume in root_partition.subvolumes:
+ if subvolume.root is True:
+ options_entry = f"rootflags=subvol={subvolume.name} " + options_entry
+
+ # Zswap should be disabled when using zram.
+ #
+ # https://github.com/archlinux/archinstall/issues/881
+ if self.zram_enabled:
+ options_entry = "zswap.enabled=0 " + options_entry
+
+ if real_device := self.detect_encryption(root_partition):
+ # TODO: We need to detect if the encrypted device is a whole disk encryption,
+ # or simply a partition encryption. Right now we assume it's a partition (and we always have)
+ log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=logging.DEBUG)
+ entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev {options_entry}')
+ else:
+ log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=logging.DEBUG)
+ entry.write(f'options root=PARTUUID={root_partition.uuid} {options_entry}')
+
+ self.helper_flags['bootloader'] = "systemd"
+
+ return True
+
+ def add_grub_bootloader(self, boot_partition :Partition, root_partition :Partition) -> bool:
+ self.pacstrap('grub') # no need?
+
+ root_fs_type = get_mount_fs_type(root_partition.filesystem)
+
+ if real_device := self.detect_encryption(root_partition):
+ root_uuid = SysCommand(f"blkid -s UUID -o value {real_device.path}").decode().rstrip()
+ _file = "/etc/default/grub"
+ add_to_CMDLINE_LINUX = f"sed -i 's/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"cryptdevice=UUID={root_uuid}:cryptlvm rootfstype={root_fs_type}\"/'"
+ enable_CRYPTODISK = "sed -i 's/#GRUB_ENABLE_CRYPTODISK=y/GRUB_ENABLE_CRYPTODISK=y/'"
+
+ log(f"Using UUID {root_uuid} of {real_device} as encrypted root identifier.", level=logging.INFO)
+ SysCommand(f"/usr/bin/arch-chroot {self.target} {add_to_CMDLINE_LINUX} {_file}")
+ SysCommand(f"/usr/bin/arch-chroot {self.target} {enable_CRYPTODISK} {_file}")
+ else:
+ _file = "/etc/default/grub"
+ add_to_CMDLINE_LINUX = f"sed -i 's/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"rootfstype={root_fs_type}\"/'"
+ SysCommand(f"/usr/bin/arch-chroot {self.target} {add_to_CMDLINE_LINUX} {_file}")
+
+ log(f"GRUB uses {boot_partition.path} as the boot partition.", level=logging.INFO)
+ if has_uefi():
+ self.pacstrap('efibootmgr') # TODO: Do we need? Yes, but remove from minimal_installation() instead?
+ try:
+ SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB --removable')
+ except SysCallError as error:
+ raise DiskError(f"Could not install GRUB to {self.target}/boot: {error}")
+ else:
+ try:
+ SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --target=i386-pc --recheck {boot_partition.parent}')
+ except SysCallError as error:
+ raise DiskError(f"Could not install GRUB to {boot_partition.path}: {error}")
+
+ try:
+ SysCommand(f'/usr/bin/arch-chroot {self.target} grub-mkconfig -o /boot/grub/grub.cfg')
+ except SysCallError as error:
+ raise DiskError(f"Could not configure GRUB: {error}")
+
+ self.helper_flags['bootloader'] = "grub"
+
+ return True
+
+ def add_efistub_bootloader(self, boot_partition :Partition, root_partition :Partition) -> bool:
+ self.pacstrap('efibootmgr')
+
+ if not has_uefi():
+ raise HardwareIncompatibilityError
+ # TODO: Ideally we would want to check if another config
+ # points towards the same disk and/or partition.
+ # And in which case we should do some clean up.
+
+ root_fs_type = get_mount_fs_type(root_partition.filesystem)
+
+ for kernel in self.kernels:
+ # Setup the firmware entry
+
+ label = f'Arch Linux ({kernel})'
+ loader = f"/vmlinuz-{kernel}"
+
+ kernel_parameters = []
+
+ if not is_vm():
+ vendor = cpu_vendor()
+ if vendor == "AuthenticAMD":
+ kernel_parameters.append("initrd=\\amd-ucode.img")
+ elif vendor == "GenuineIntel":
+ kernel_parameters.append("initrd=\\intel-ucode.img")
+ else:
+ self.log("unknow cpu vendor, not adding ucode to firmware boot entry")
+
+ kernel_parameters.append(f"initrd=\\initramfs-{kernel}.img")
+
+ # blkid doesn't trigger on loopback devices really well,
+ # so we'll use the old manual method until we get that sorted out.
+ if real_device := self.detect_encryption(root_partition):
+ # TODO: We need to detect if the encrypted device is a whole disk encryption,
+ # or simply a partition encryption. Right now we assume it's a partition (and we always have)
+ log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=logging.DEBUG)
+ kernel_parameters.append(f'cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}')
+ else:
+ log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=logging.DEBUG)
+ kernel_parameters.append(f'root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}')
+
+ SysCommand(f'efibootmgr --disk {boot_partition.path[:-1]} --part {boot_partition.path[-1]} --create --label "{label}" --loader {loader} --unicode \'{" ".join(kernel_parameters)}\' --verbose')
+
+ self.helper_flags['bootloader'] = "efistub"
+
+ return True
+
def add_bootloader(self, bootloader :str = 'systemd-bootctl') -> bool:
"""
Adds a bootloader to the installation instance.
@@ -648,177 +835,23 @@ class Installer:
boot_partition = None
root_partition = None
- root_partition_fs = None
for partition in self.partitions:
- if partition.mountpoint == self.target + '/boot':
+ if partition.mountpoint == os.path.join(self.target, 'boot'):
boot_partition = partition
elif partition.mountpoint == self.target:
root_partition = partition
- root_partition_fs = partition.filesystem
- root_fs_type = get_mount_fs_type(root_partition_fs)
if boot_partition is None or root_partition is None:
- raise ValueError(f"Could not detect root (/) or boot (/boot) in {self.target} based on: {self.partitions}")
+ raise ValueError(f"Could not detect root ({root_partition}) or boot ({boot_partition}) in {self.target} based on: {self.partitions}")
self.log(f'Adding bootloader {bootloader} to {boot_partition if boot_partition else root_partition}', level=logging.INFO)
if bootloader == 'systemd-bootctl':
- self.pacstrap('efibootmgr')
-
- if not has_uefi():
- raise HardwareIncompatibilityError
- # TODO: Ideally we would want to check if another config
- # points towards the same disk and/or partition.
- # And in which case we should do some clean up.
-
- # Install the boot loader
- if SysCommand(f'/usr/bin/arch-chroot {self.target} bootctl --path=/boot install').exit_code != 0:
- # Fallback, try creating the boot loader without touching the EFI variables
- SysCommand(f'/usr/bin/arch-chroot {self.target} bootctl --no-variables --path=/boot install')
-
- # Ensure that the /boot/loader directory exists before we try to create files in it
- if not os.path.exists(f'{self.target}/boot/loader'):
- os.makedirs(f'{self.target}/boot/loader')
-
- # Modify or create a loader.conf
- if os.path.isfile(f'{self.target}/boot/loader/loader.conf'):
- with open(f'{self.target}/boot/loader/loader.conf', 'r') as loader:
- loader_data = loader.read().split('\n')
- else:
- loader_data = [
- f"default {self.init_time}",
- "timeout 5"
- ]
-
- with open(f'{self.target}/boot/loader/loader.conf', 'w') as loader:
- for line in loader_data:
- if line[:8] == 'default ':
- loader.write(f'default {self.init_time}_{self.kernels[0]}\n')
- elif line[:8] == '#timeout' and 'timeout 5' not in loader_data:
- # We add in the default timeout to support dual-boot
- loader.write(f"{line[1:]}\n")
- else:
- loader.write(f"{line}\n")
-
- # Ensure that the /boot/loader/entries directory exists before we try to create files in it
- if not os.path.exists(f'{self.target}/boot/loader/entries'):
- os.makedirs(f'{self.target}/boot/loader/entries')
-
- for kernel in self.kernels:
- # Setup the loader entry
- with open(f'{self.target}/boot/loader/entries/{self.init_time}_{kernel}.conf', 'w') as entry:
- entry.write('# Created by: archinstall\n')
- entry.write(f'# Created on: {self.init_time}\n')
- entry.write(f'title Arch Linux ({kernel})\n')
- entry.write(f"linux /vmlinuz-{kernel}\n")
- if not is_vm():
- vendor = cpu_vendor()
- if vendor == "AuthenticAMD":
- entry.write("initrd /amd-ucode.img\n")
- elif vendor == "GenuineIntel":
- entry.write("initrd /intel-ucode.img\n")
- else:
- self.log("unknow cpu vendor, not adding ucode to systemd-boot config")
- entry.write(f"initrd /initramfs-{kernel}.img\n")
- # blkid doesn't trigger on loopback devices really well,
- # so we'll use the old manual method until we get that sorted out.
- if root_fs_type is not None:
- options_entry = f'rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}\n'
- else:
- options_entry = f'rw intel_pstate=no_hwp {" ".join(self.KERNEL_PARAMS)}\n'
- base_path,bind_path = split_bind_name(str(root_partition.path))
- if bind_path is not None: # and root_fs_type == 'btrfs':
- options_entry = f"rootflags=subvol={bind_path} " + options_entry
-
- # Zswap should be disabled when using zram.
- #
- # https://github.com/archlinux/archinstall/issues/881
- if self.zram_enabled:
- options_entry = "zswap.enabled=0 " + options_entry
-
- if real_device := self.detect_encryption(root_partition):
- # TODO: We need to detect if the encrypted device is a whole disk encryption,
- # or simply a partition encryption. Right now we assume it's a partition (and we always have)
- log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=logging.DEBUG)
- entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev {options_entry}')
- else:
- log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=logging.DEBUG)
- entry.write(f'options root=PARTUUID={root_partition.uuid} {options_entry}')
-
- self.helper_flags['bootloader'] = bootloader
-
+ self.add_systemd_bootloader(boot_partition, root_partition)
elif bootloader == "grub-install":
- self.pacstrap('grub') # no need?
-
- if real_device := self.detect_encryption(root_partition):
- root_uuid = SysCommand(f"blkid -s UUID -o value {real_device.path}").decode().rstrip()
- _file = "/etc/default/grub"
- add_to_CMDLINE_LINUX = f"sed -i 's/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"cryptdevice=UUID={root_uuid}:cryptlvm rootfstype={root_fs_type}\"/'"
- enable_CRYPTODISK = "sed -i 's/#GRUB_ENABLE_CRYPTODISK=y/GRUB_ENABLE_CRYPTODISK=y/'"
-
- log(f"Using UUID {root_uuid} of {real_device} as encrypted root identifier.", level=logging.INFO)
- SysCommand(f"/usr/bin/arch-chroot {self.target} {add_to_CMDLINE_LINUX} {_file}")
- SysCommand(f"/usr/bin/arch-chroot {self.target} {enable_CRYPTODISK} {_file}")
- else:
- _file = "/etc/default/grub"
- add_to_CMDLINE_LINUX = f"sed -i 's/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"rootfstype={root_fs_type}\"/'"
- SysCommand(f"/usr/bin/arch-chroot {self.target} {add_to_CMDLINE_LINUX} {_file}")
-
- log(f"GRUB uses {boot_partition.path} as the boot partition.", level=logging.INFO)
- if has_uefi():
- self.pacstrap('efibootmgr') # TODO: Do we need? Yes, but remove from minimal_installation() instead?
- if not (handle := SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB --removable')).exit_code == 0:
- raise DiskError(f"Could not install GRUB to {self.target}/boot: {handle}")
- else:
- if not (handle := SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --target=i386-pc --recheck {boot_partition.parent}')).exit_code == 0:
- raise DiskError(f"Could not install GRUB to {boot_partition.path}: {handle}")
-
- if not (handle := SysCommand(f'/usr/bin/arch-chroot {self.target} grub-mkconfig -o /boot/grub/grub.cfg')).exit_code == 0:
- raise DiskError(f"Could not configure GRUB: {handle}")
-
- self.helper_flags['bootloader'] = True
+ self.add_grub_bootloader(boot_partition, root_partition)
elif bootloader == 'efistub':
- self.pacstrap('efibootmgr')
-
- if not has_uefi():
- raise HardwareIncompatibilityError
- # TODO: Ideally we would want to check if another config
- # points towards the same disk and/or partition.
- # And in which case we should do some clean up.
-
- for kernel in self.kernels:
- # Setup the firmware entry
-
- label = f'Arch Linux ({kernel})'
- loader = f"/vmlinuz-{kernel}"
-
- kernel_parameters = []
-
- if not is_vm():
- vendor = cpu_vendor()
- if vendor == "AuthenticAMD":
- kernel_parameters.append("initrd=\\amd-ucode.img")
- elif vendor == "GenuineIntel":
- kernel_parameters.append("initrd=\\intel-ucode.img")
- else:
- self.log("unknow cpu vendor, not adding ucode to firmware boot entry")
-
- kernel_parameters.append(f"initrd=\\initramfs-{kernel}.img")
-
- # blkid doesn't trigger on loopback devices really well,
- # so we'll use the old manual method until we get that sorted out.
- if real_device := self.detect_encryption(root_partition):
- # TODO: We need to detect if the encrypted device is a whole disk encryption,
- # or simply a partition encryption. Right now we assume it's a partition (and we always have)
- log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=logging.DEBUG)
- kernel_parameters.append(f'cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}')
- else:
- log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=logging.DEBUG)
- kernel_parameters.append(f'root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}')
-
- SysCommand(f'efibootmgr --disk {boot_partition.path[:-1]} --part {boot_partition.path[-1]} --create --label "{label}" --loader {loader} --unicode \'{" ".join(kernel_parameters)}\' --verbose')
-
- self.helper_flags['bootloader'] = bootloader
+ self.add_efistub_bootloader(boot_partition, root_partition)
else:
raise RequirementError(f"Unknown (or not yet implemented) bootloader requested: {bootloader}")