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.py237
1 files changed, 149 insertions, 88 deletions
diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py
index 1926f593..f1c7b3db 100644
--- a/archinstall/lib/installer.py
+++ b/archinstall/lib/installer.py
@@ -132,6 +132,7 @@ class Installer:
# if HSM is not used to encrypt the root volume. Check mkinitcpio() function for that override.
self.HOOKS = ["base", "systemd", "autodetect", "keyboard", "sd-vconsole", "modconf", "block", "filesystems", "fsck"]
self.KERNEL_PARAMS = []
+ self.FSTAB_ENTRIES = []
self._zram_enabled = False
@@ -198,7 +199,7 @@ class Installer:
def _create_keyfile(self,luks_handle , partition :dict, password :str):
""" roiutine to create keyfiles, so it can be moved elsewhere
"""
- if self._disk_encryption.generate_encryption_file(partition):
+ if self._disk_encryption and self._disk_encryption.generate_encryption_file(partition):
if not (cryptkey_dir := pathlib.Path(f"{self.target}/etc/cryptsetup-keys.d")).exists():
cryptkey_dir.mkdir(parents=True)
# Once we store the key as ../xyzloop.key systemd-cryptsetup can automatically load this key
@@ -246,20 +247,21 @@ class Installer:
mount_queue = {}
# we manage the encrypted partititons
- for partition in self._disk_encryption.partitions:
- # open the luks device and all associate stuff
- loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['device_instance'].path).name}"
-
- # note that we DON'T auto_unmount (i.e. close the encrypted device so it can be used
- with (luks_handle := luks2(partition['device_instance'], loopdev, self._disk_encryption.encryption_password, auto_unmount=False)) as unlocked_device:
- if self._disk_encryption.generate_encryption_file(partition) and not self._has_root(partition):
- list_luks_handles.append([luks_handle, partition, self._disk_encryption.encryption_password])
- # this way all the requesrs will be to the dm_crypt device and not to the physical partition
- partition['device_instance'] = unlocked_device
-
- if self._has_root(partition) and self._disk_encryption.generate_encryption_file(partition) is False:
- if self._disk_encryption.hsm_device:
- Fido2.fido2_enroll(self._disk_encryption.hsm_device, partition['device_instance'], self._disk_encryption.encryption_password)
+ if self._disk_encryption:
+ for partition in self._disk_encryption.all_partitions:
+ # open the luks device and all associate stuff
+ loopdev = f"{storage.get('ENC_IDENTIFIER', 'ai')}{pathlib.Path(partition['device_instance'].path).name}"
+
+ # note that we DON'T auto_unmount (i.e. close the encrypted device so it can be used
+ with (luks_handle := luks2(partition['device_instance'], loopdev, self._disk_encryption.encryption_password, auto_unmount=False)) as unlocked_device:
+ if self._disk_encryption.generate_encryption_file(partition) and not self._has_root(partition):
+ list_luks_handles.append([luks_handle, partition, self._disk_encryption.encryption_password])
+ # this way all the requesrs will be to the dm_crypt device and not to the physical partition
+ partition['device_instance'] = unlocked_device
+
+ if self._has_root(partition) and self._disk_encryption.generate_encryption_file(partition) is False:
+ if self._disk_encryption.hsm_device:
+ Fido2.fido2_enroll(self._disk_encryption.hsm_device, partition['device_instance'], self._disk_encryption.encryption_password)
btrfs_subvolumes = [entry for entry in list_part if entry.get('btrfs', {}).get('subvolumes', [])]
@@ -292,7 +294,7 @@ class Installer:
else:
mount_queue[mountpoint] = lambda instance=partition['device_instance'], target=f"{self.target}{mountpoint}": instance.mount(target)
- log(f"Using mount order: {list(sorted(mount_queue.items(), key=lambda item: item[0]))}", level=logging.INFO, fg="white")
+ log(f"Using mount order: {list(sorted(mount_queue.items(), key=lambda item: item[0]))}", level=logging.DEBUG, fg="white")
# We mount everything by sorting on the mountpoint itself.
for mountpoint, frozen_func in sorted(mount_queue.items(), key=lambda item: item[0]):
@@ -317,6 +319,26 @@ class Installer:
partition.mount(f'{self.target}{mountpoint}', options=options)
+ def add_swapfile(self, size='4G', enable_resume=True, file='/swapfile'):
+ if file[:1] != '/':
+ file = f"/{file}"
+ if len(file.strip()) <= 0 or file == '/':
+ raise ValueError(f"The filename for the swap file has to be a valid path, not: {self.target}{file}")
+
+ SysCommand(f'dd if=/dev/zero of={self.target}{file} bs={size} count=1')
+ SysCommand(f'chmod 0600 {self.target}{file}')
+ SysCommand(f'mkswap {self.target}{file}')
+
+ self.FSTAB_ENTRIES.append(f'{file} none swap defaults 0 0')
+
+ if enable_resume:
+ resume_uuid = SysCommand(f'findmnt -no UUID -T {self.target}{file}').decode('UTF-8').strip()
+ resume_offset = SysCommand(f'/usr/bin/filefrag -v {self.target}{file}').decode('UTF-8').split('0:', 1)[1].split(":", 1)[1].split("..", 1)[0].strip()
+
+ self.HOOKS.append('resume')
+ self.KERNEL_PARAMS.append(f'resume=UUID={resume_uuid}')
+ self.KERNEL_PARAMS.append(f'resume_offset={resume_offset}')
+
def post_install_check(self, *args :str, **kwargs :str) -> List[str]:
return [step for step, flag in self.helper_flags.items() if flag is False]
@@ -396,7 +418,8 @@ class Installer:
raise RequirementError(f'Could not sync mirrors: {error}', level=logging.ERROR, fg="red")
try:
- return SysCommand(f'/usr/bin/pacstrap -C /etc/pacman.conf {self.target} {" ".join(packages)} --noconfirm', peak_output=True).exit_code == 0
+ SysCommand(f'/usr/bin/pacstrap -C /etc/pacman.conf -K {self.target} {" ".join(packages)} --noconfirm', peek_output=True)
+ return True
except SysCallError as error:
self.log(f'Could not strap in packages: {error}', level=logging.ERROR, fg="red")
@@ -417,8 +440,10 @@ class Installer:
def genfstab(self, flags :str = '-pU') -> bool:
self.log(f"Updating {self.target}/etc/fstab", level=logging.INFO)
- if not (fstab := SysCommand(f'/usr/bin/genfstab {flags} {self.target}')).exit_code == 0:
- raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n Error: {fstab}')
+ try:
+ fstab = SysCommand(f'/usr/bin/genfstab {flags} {self.target}')
+ except SysCallError as error:
+ raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n Error: {error}')
with open(f"{self.target}/etc/fstab", 'a') as fstab_fh:
fstab_fh.write(fstab.decode())
@@ -431,6 +456,10 @@ class Installer:
if plugin.on_genfstab(self) is True:
break
+ with open(f"{self.target}/etc/fstab", 'a') as fstab_fh:
+ for entry in self.FSTAB_ENTRIES:
+ fstab_fh.write(f'{entry}\n')
+
return True
def set_hostname(self, hostname: str, *args :str, **kwargs :str) -> None:
@@ -463,7 +492,11 @@ class Installer:
with open(f'{self.target}/etc/locale.conf', 'w') as fh:
fh.write(f'LANG={locale}.{encoding}{modifier}\n')
- return True if SysCommand(f'/usr/bin/arch-chroot {self.target} locale-gen').exit_code == 0 else False
+ try:
+ SysCommand(f'/usr/bin/arch-chroot {self.target} locale-gen')
+ return True
+ except SysCallError:
+ return False
def set_timezone(self, zone :str, *args :str, **kwargs :str) -> bool:
if not zone:
@@ -519,8 +552,10 @@ class Installer:
def enable_service(self, *services :str) -> None:
for service in services:
self.log(f'Enabling service {service}', level=logging.INFO)
- if (output := self.arch_chroot(f'systemctl enable {service}')).exit_code != 0:
- raise ServiceException(f"Unable to start service {service}: {output}")
+ try:
+ self.arch_chroot(f'systemctl enable {service}')
+ except SysCallError as error:
+ raise ServiceException(f"Unable to start service {service}: {error}")
for plugin in plugins.values():
if hasattr(plugin, 'on_service'):
@@ -641,12 +676,17 @@ class Installer:
if plugin.on_mkinitcpio(self):
return True
+ # mkinitcpio will error out if there's no vconsole.
+ if (vconsole := pathlib.Path(f"{self.target}/etc/vconsole.conf")).exists() is False:
+ with vconsole.open('w') as fh:
+ fh.write(f"KEYMAP={storage['arguments']['keyboard-layout']}\n")
+
with open(f'{self.target}/etc/mkinitcpio.conf', 'w') as mkinit:
mkinit.write(f"MODULES=({' '.join(self.MODULES)})\n")
mkinit.write(f"BINARIES=({' '.join(self.BINARIES)})\n")
mkinit.write(f"FILES=({' '.join(self.FILES)})\n")
- if not self._disk_encryption.hsm_device:
+ if self._disk_encryption and not self._disk_encryption.hsm_device:
# For now, if we don't use HSM we revert to the old
# way of setting up encryption hooks for mkinitcpio.
# This is purely for stability reasons, we're going away from this.
@@ -656,7 +696,11 @@ class Installer:
mkinit.write(f"HOOKS=({' '.join(self.HOOKS)})\n")
- return SysCommand(f'/usr/bin/arch-chroot {self.target} mkinitcpio {" ".join(flags)}').exit_code == 0
+ try:
+ SysCommand(f'/usr/bin/arch-chroot {self.target} mkinitcpio {" ".join(flags)}')
+ return True
+ except SysCallError:
+ return False
def minimal_installation(
self, testing: bool = False, multilib: bool = False,
@@ -690,7 +734,7 @@ class Installer:
self.HOOKS.remove('fsck')
if self.detect_encryption(partition):
- if self._disk_encryption.hsm_device:
+ if self._disk_encryption and self._disk_encryption.hsm_device:
# Required bby mkinitcpio to add support for fido2-device options
self.pacstrap('libfido2')
@@ -754,14 +798,6 @@ class Installer:
# TODO: Use python functions for this
SysCommand(f'/usr/bin/arch-chroot {self.target} chmod 700 /root')
- if self._disk_encryption.hsm_device:
- # TODO:
- # A bit of a hack, but we need to get vconsole.conf in there
- # before running `mkinitcpio` because it expects it in HSM mode.
- if (vconsole := pathlib.Path(f"{self.target}/etc/vconsole.conf")).exists() is False:
- with vconsole.open('w') as fh:
- fh.write(f"KEYMAP={storage['arguments']['keyboard-layout']}\n")
-
self.mkinitcpio('-P')
self.helper_flags['base'] = True
@@ -841,60 +877,68 @@ class Installer:
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")
+ for variant in ("", "-fallback"):
+ # Setup the loader entry
+ with open(f'{self.target}/boot/loader/entries/{self.init_time}_{kernel}{variant}.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}{variant})\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(f"Unknown CPU vendor '{vendor}' detected. Archinstall won't add any ucode to systemd-boot config.", level=logging.DEBUG)
+ entry.write(f"initrd /initramfs-{kernel}{variant}.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 rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}\n'
else:
- self.log(f"Unknown CPU vendor '{vendor}' detected. Archinstall won't add any ucode to systemd-boot config.", level=logging.DEBUG)
- 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 rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}\n'
- else:
- options_entry = f'rw {" ".join(self.KERNEL_PARAMS)}\n'
+ options_entry = f'rw {" ".join(self.KERNEL_PARAMS)}\n'
- for subvolume in root_partition.subvolumes:
- if subvolume.root is True and subvolume.name != '<FS_TREE>':
- options_entry = f"rootflags=subvol={subvolume.name} " + options_entry
+ for subvolume in root_partition.subvolumes:
+ if subvolume.root is True and subvolume.name != '<FS_TREE>':
+ 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
+ # 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}/{real_device.part_uuid}'.", level=logging.DEBUG)
+ 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}/{real_device.part_uuid}'.", level=logging.DEBUG)
- kernel_options = f"options"
+ kernel_options = f"options"
- if self._disk_encryption.hsm_device:
+ if self._disk_encryption.hsm_device:
+ # Note: lsblk UUID must be used, not PARTUUID for sd-encrypt to work
+ kernel_options += f" rd.luks.name={real_device.uuid}=luksdev"
+ # Note: tpm2-device and fido2-device don't play along very well:
+ # https://github.com/archlinux/archinstall/pull/1196#issuecomment-1129715645
+ kernel_options += f" rd.luks.options=fido2-device=auto,password-echo=no"
+ else:
+ kernel_options += f" cryptdevice=PARTUUID={real_device.part_uuid}:luksdev"
+
+ entry.write(f'{kernel_options} root=/dev/mapper/luksdev {options_entry}')
+
+ if self._disk_encryption and self._disk_encryption.hsm_device:
# Note: lsblk UUID must be used, not PARTUUID for sd-encrypt to work
kernel_options += f" rd.luks.name={real_device.uuid}=luksdev"
# Note: tpm2-device and fido2-device don't play along very well:
# https://github.com/archlinux/archinstall/pull/1196#issuecomment-1129715645
kernel_options += f" rd.luks.options=fido2-device=auto,password-echo=no"
else:
- kernel_options += f" cryptdevice=PARTUUID={real_device.part_uuid}:luksdev"
-
- entry.write(f'{kernel_options} root=/dev/mapper/luksdev {options_entry}')
- else:
- log(f"Identifying root partition by PARTUUID on {root_partition}, looking for '{root_partition.part_uuid}'.", level=logging.DEBUG)
- entry.write(f'options root=PARTUUID={root_partition.part_uuid} {options_entry}')
+ log(f"Identifying root partition by PARTUUID on {root_partition}, looking for '{root_partition.part_uuid}'.", level=logging.DEBUG)
+ entry.write(f'options root=PARTUUID={root_partition.part_uuid} {options_entry}')
self.helper_flags['bootloader'] = "systemd"
@@ -923,15 +967,15 @@ class Installer:
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 --debug --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB --removable', peak_output=True)
+ SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --debug --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB --removable', peek_output=True)
except SysCallError:
try:
- SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --debug --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB --removable', peak_output=True)
+ SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --debug --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB --removable', peek_output=True)
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 --debug --target=i386-pc --recheck {boot_partition.parent}', peak_output=True)
+ SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --debug --target=i386-pc --recheck {boot_partition.parent}', peek_output=True)
except SysCallError as error:
raise DiskError(f"Could not install GRUB to {boot_partition.path}: {error}")
@@ -1109,8 +1153,10 @@ class Installer:
if not handled_by_plugin:
self.log(f'Creating user {user}', level=logging.INFO)
- if not (output := SysCommand(f'/usr/bin/arch-chroot {self.target} useradd -m -G wheel {user}')).exit_code == 0:
- raise SystemError(f"Could not create user inside installation: {output}")
+ try:
+ SysCommand(f'/usr/bin/arch-chroot {self.target} useradd -m -G wheel {user}')
+ except SysCallError as error:
+ raise SystemError(f"Could not create user inside installation: {error}")
for plugin in plugins.values():
if hasattr(plugin, 'on_user_created'):
@@ -1138,17 +1184,28 @@ class Installer:
echo = shlex.join(['echo', combo])
sh = shlex.join(['sh', '-c', echo])
- result = SysCommand(f"/usr/bin/arch-chroot {self.target} " + sh[:-1] + " | chpasswd'")
- return result.exit_code == 0
+ try:
+ SysCommand(f"/usr/bin/arch-chroot {self.target} " + sh[:-1] + " | chpasswd'")
+ return True
+ except SysCallError:
+ return False
def user_set_shell(self, user :str, shell :str) -> bool:
self.log(f'Setting shell for {user} to {shell}', level=logging.INFO)
- return SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\"").exit_code == 0
+ try:
+ SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\"")
+ return True
+ except SysCallError:
+ return False
def chown(self, owner :str, path :str, options :List[str] = []) -> bool:
cleaned_path = path.replace('\'', '\\\'')
- return SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c 'chown {' '.join(options)} {owner} {cleaned_path}'").exit_code == 0
+ try:
+ SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c 'chown {' '.join(options)} {owner} {cleaned_path}'")
+ return True
+ except SysCallError:
+ return False
def create_file(self, filename :str, owner :Optional[str] = None) -> InstallationFile:
return InstallationFile(self, filename, owner)
@@ -1166,8 +1223,10 @@ class Installer:
with Boot(self) as session:
os.system('/usr/bin/systemd-run --machine=archinstall --pty localectl set-keymap ""')
- if (output := session.SysCommand(["localectl", "set-keymap", language])).exit_code != 0:
- raise ServiceException(f"Unable to set locale '{language}' for console: {output}")
+ try:
+ session.SysCommand(["localectl", "set-keymap", language])
+ except SysCallError as error:
+ raise ServiceException(f"Unable to set locale '{language}' for console: {error}")
self.log(f"Keyboard language for this installation is now set to: {language}")
else:
@@ -1190,8 +1249,10 @@ class Installer:
with Boot(self) as session:
session.SysCommand(["localectl", "set-x11-keymap", '""'])
- if (output := session.SysCommand(["localectl", "set-x11-keymap", language])).exit_code != 0:
- raise ServiceException(f"Unable to set locale '{language}' for X11: {output}")
+ try:
+ session.SysCommand(["localectl", "set-x11-keymap", language])
+ except SysCallError as error:
+ raise ServiceException(f"Unable to set locale '{language}' for X11: {error}")
else:
self.log(f'X11-Keyboard language was not changed from default (no language specified).', fg="yellow", level=logging.INFO)