index : archinstall32 | |
Archlinux32 installer | gitolite user |
summaryrefslogtreecommitdiff |
-rw-r--r-- | archinstall/lib/installer.py | 237 |
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) |