index : archinstall32 | |
Archlinux32 installer | gitolite user |
summaryrefslogtreecommitdiff |
author | Anton Hvornum <anton@hvornum.se> | 2021-04-12 00:09:55 +0200 |
---|---|---|
committer | Anton Hvornum <anton@hvornum.se> | 2021-04-12 00:09:55 +0200 |
commit | 398f95ee563be90d84cc943baf88943c078abe03 (patch) | |
tree | 850c595fea440d8681310eecf117c25a94de82f2 /archinstall/lib | |
parent | 94c31222fab7d5fa02f6f2393893e248d64a8dcb (diff) | |
parent | be45268d0bca2ee978d82d8361a12c5025a8baae (diff) |
-rw-r--r-- | archinstall/lib/disk.py | 67 | ||||
-rw-r--r-- | archinstall/lib/exceptions.py | 2 | ||||
-rw-r--r-- | archinstall/lib/installer.py | 241 | ||||
-rw-r--r-- | archinstall/lib/luks.py | 9 | ||||
-rw-r--r-- | archinstall/lib/profiles.py | 46 | ||||
-rw-r--r-- | archinstall/lib/user_interaction.py | 52 |
diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index e2f4d76e..67c2bdcd 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -127,6 +127,18 @@ class BlockDevice(): def partition_table_type(self): return GPT + @property + def uuid(self): + log(f'BlockDevice().uuid is untested!', level=LOG_LEVELS.Warning, fg='yellow') + """ + Returns the disk UUID as returned by lsblk. + This is more reliable than relying on /dev/disk/by-partuuid as + it doesn't seam to be able to detect md raid partitions. + """ + lsblk = b''.join(sys_command(f'lsblk -J -o+UUID {self.path}')) + for partition in json.loads(lsblk.decode('UTF-8'))['blockdevices']: + return partition.get('uuid', None) + def has_partitions(self): return len(self.partitions) @@ -167,7 +179,7 @@ class Partition(): self.mountpoint = target if not self.filesystem and autodetect_filesystem: - if (fstype := mount_information.get('fstype', get_filesystem_type(self.real_device))): + if (fstype := mount_information.get('fstype', get_filesystem_type(path))): self.filesystem = fstype if self.filesystem == 'crypto_LUKS': @@ -188,9 +200,9 @@ class Partition(): mount_repr = f", rel_mountpoint={self.target_mountpoint}" if self._encrypted: - return f'Partition(path={self.path}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})' + return f'Partition(path={self.path}, size={self.size}, real_device={self.real_device}, fs={self.filesystem}{mount_repr})' else: - return f'Partition(path={self.path}, fs={self.filesystem}{mount_repr})' + return f'Partition(path={self.path}, size={self.size}, fs={self.filesystem}{mount_repr})' @property def uuid(self) -> str: @@ -216,13 +228,14 @@ class Partition(): self._encrypted = value @property + def parent(self): + return self.real_device + + @property def real_device(self): - if not self._encrypted: - return self.path - else: - for blockdevice in json.loads(b''.join(sys_command(['lsblk', '-J'])).decode('UTF-8'))['blockdevices']: - if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))): - return f"/dev/{parent}" + for blockdevice in json.loads(b''.join(sys_command('lsblk -J')).decode('UTF-8'))['blockdevices']: + if (parent := self.find_parent_of(blockdevice, os.path.basename(self.path))): + return f"/dev/{parent}" # raise DiskError(f'Could not find appropriate parent for encrypted partition {self}') return self.path @@ -367,14 +380,16 @@ class Partition(): if not fs: if not self.filesystem: raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.') fs = self.filesystem - ## libc has some issues with loop devices, defaulting back to sys calls - # ret = libc.mount(self.path.encode(), target.encode(), fs.encode(), 0, options.encode()) - # if ret < 0: - # errno = ctypes.get_errno() - # raise OSError(errno, f"Error mounting {self.path} ({fs}) on {target} with options '{options}': {os.strerror(errno)}") - if sys_command(f'/usr/bin/mount {self.path} {target}').exit_code == 0: - self.mountpoint = target - return True + + pathlib.Path(target).mkdir(parents=True, exist_ok=True) + + try: + sys_command(f'/usr/bin/mount {self.path} {target}') + except SysCallError as err: + raise err + + self.mountpoint = target + return True def unmount(self): try: @@ -589,6 +604,24 @@ def get_mount_info(path): return output['filesystems'][0] +def get_partitions_in_use(mountpoint): + try: + output = b''.join(sys_command(f'/usr/bin/findmnt --json -R {mountpoint}')) + except SysCallError: + return {} + + mounts = [] + + output = output.decode('UTF-8') + output = json.loads(output) + for target in output.get('filesystems', []): + mounts.append(Partition(target['source'], None, filesystem=target.get('fstype', None), mountpoint=target['target'])) + + for child in target.get('children', []): + mounts.append(Partition(child['source'], None, filesystem=child.get('fstype', None), mountpoint=child['target'])) + + return mounts + def get_filesystem_type(path): try: handle = sys_command(f"blkid -o value -s TYPE {path}") diff --git a/archinstall/lib/exceptions.py b/archinstall/lib/exceptions.py index a320eef6..49913980 100644 --- a/archinstall/lib/exceptions.py +++ b/archinstall/lib/exceptions.py @@ -18,4 +18,6 @@ class HardwareIncompatibilityError(BaseException): class PermissionError(BaseException): pass class UserError(BaseException): + pass +class ServiceException(BaseException): pass
\ No newline at end of file diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 484e7407..2effc7f7 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -39,30 +39,21 @@ class Installer(): :type hostname: str, optional """ - def __init__(self, partition, boot_partition, *, base_packages=__base_packages__, profile=None, mountpoint='/mnt', hostname='ArchInstalled', logdir=None, logfile=None): - self.profile = profile - self.hostname = hostname - self.mountpoint = mountpoint + def __init__(self, target, *, base_packages='base base-devel linux linux-firmware efibootmgr'): + self.target = target self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S') self.milliseconds = int(str(time.time()).split('.')[1]) - if logdir: - storage['LOG_PATH'] = logdir - if logfile: - storage['LOG_FILE'] = logfile - self.helper_flags = { - 'bootloader' : False, 'base' : False, - 'user' : False # Root counts as a user, if additional users are skipped. + 'bootloader' : False } self.base_packages = base_packages.split(' ') if type(base_packages) is str else base_packages self.post_base_install = [] - storage['session'] = self - self.partition = partition - self.boot_partition = boot_partition + storage['session'] = self + self.partitions = get_partitions_in_use(self.target) def log(self, *args, level=LOG_LEVELS.Debug, **kwargs): """ @@ -72,11 +63,6 @@ class Installer(): log(*args, level=level, **kwargs) def __enter__(self, *args, **kwargs): - if hasUEFI(): - # on bios we don't have a boot partition - self.partition.mount(self.mountpoint) - os.makedirs(f'{self.mountpoint}/boot', exist_ok=True) - self.boot_partition.mount(f'{self.mountpoint}/boot') return self def __exit__(self, *args, **kwargs): @@ -119,18 +105,18 @@ class Installer(): if (filename := storage.get('LOG_FILE', None)): absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename) - if not os.path.isdir(f"{self.mountpoint}/{os.path.dirname(absolute_logfile)}"): - os.makedirs(f"{self.mountpoint}/{os.path.dirname(absolute_logfile)}") + if not os.path.isdir(f"{self.target}/{os.path.dirname(absolute_logfile)}"): + os.makedirs(f"{self.target}/{os.path.dirname(absolute_logfile)}") - shutil.copy2(absolute_logfile, f"{self.mountpoint}/{absolute_logfile}") + shutil.copy2(absolute_logfile, f"{self.target}/{absolute_logfile}") return True def mount(self, partition, mountpoint, create_mountpoint=True): - if create_mountpoint and not os.path.isdir(f'{self.mountpoint}{mountpoint}'): - os.makedirs(f'{self.mountpoint}{mountpoint}') + if create_mountpoint and not os.path.isdir(f'{self.target}{mountpoint}'): + os.makedirs(f'{self.target}{mountpoint}') - partition.mount(f'{self.mountpoint}{mountpoint}') + partition.mount(f'{self.target}{mountpoint}') def post_install_check(self, *args, **kwargs): return [step for step, flag in self.helper_flags.items() if flag is False] @@ -140,7 +126,7 @@ class Installer(): self.log(f'Installing packages: {packages}', level=LOG_LEVELS.Info) if (sync_mirrors := sys_command('/usr/bin/pacman -Syy')).exit_code == 0: - if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.mountpoint} {" ".join(packages)}', peak_output=True, **kwargs)).exit_code == 0: + if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.target} {" ".join(packages)}', **kwargs)).exit_code == 0: return True else: self.log(f'Could not strap in packages: {pacstrap.exit_code}', level=LOG_LEVELS.Info) @@ -148,42 +134,41 @@ class Installer(): self.log(f'Could not sync mirrors: {sync_mirrors.exit_code}', level=LOG_LEVELS.Info) def set_mirrors(self, mirrors): - return use_mirrors(mirrors, destination=f'{self.mountpoint}/etc/pacman.d/mirrorlist') + return use_mirrors(mirrors, destination=f'{self.target}/etc/pacman.d/mirrorlist') def genfstab(self, flags='-pU'): - self.log(f"Updating {self.mountpoint}/etc/fstab", level=LOG_LEVELS.Info) + self.log(f"Updating {self.target}/etc/fstab", level=LOG_LEVELS.Info) - fstab = sys_command(f'/usr/bin/genfstab {flags} {self.mountpoint}').trace_log - with open(f"{self.mountpoint}/etc/fstab", 'ab') as fstab_fh: + fstab = sys_command(f'/usr/bin/genfstab {flags} {self.target}').trace_log + with open(f"{self.target}/etc/fstab", 'ab') as fstab_fh: fstab_fh.write(fstab) - if not os.path.isfile(f'{self.mountpoint}/etc/fstab'): + if not os.path.isfile(f'{self.target}/etc/fstab'): raise RequirementError(f'Could not generate fstab, strapping in packages most likely failed (disk out of space?)\n{fstab}') return True - def set_hostname(self, hostname=None, *args, **kwargs): - if not hostname: hostname = self.hostname - with open(f'{self.mountpoint}/etc/hostname', 'w') as fh: - fh.write(self.hostname + '\n') + def set_hostname(self, hostname :str, *args, **kwargs): + with open(f'{self.target}/etc/hostname', 'w') as fh: + fh.write(hostname + '\n') def set_locale(self, locale, encoding='UTF-8', *args, **kwargs): if not len(locale): return True - with open(f'{self.mountpoint}/etc/locale.gen', 'a') as fh: + with open(f'{self.target}/etc/locale.gen', 'a') as fh: fh.write(f'{locale}.{encoding} {encoding}\n') - with open(f'{self.mountpoint}/etc/locale.conf', 'w') as fh: + with open(f'{self.target}/etc/locale.conf', 'w') as fh: fh.write(f'LANG={locale}.{encoding}\n') - return True if sys_command(f'/usr/bin/arch-chroot {self.mountpoint} locale-gen').exit_code == 0 else False + return True if sys_command(f'/usr/bin/arch-chroot {self.target} locale-gen').exit_code == 0 else False def set_timezone(self, zone, *args, **kwargs): if not zone: return True if not len(zone): return True # Redundant if (pathlib.Path("/usr")/"share"/"zoneinfo"/zone).exists(): - (pathlib.Path(self.mountpoint)/"etc"/"localtime").unlink(missing_ok=True) - sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{zone} /etc/localtime') + (pathlib.Path(self.target)/"etc"/"localtime").unlink(missing_ok=True) + sys_command(f'/usr/bin/arch-chroot {self.target} ln -s /usr/share/zoneinfo/{zone} /etc/localtime') return True else: self.log( @@ -198,12 +183,14 @@ class Installer(): if self.enable_service('ntpd'): return True - def enable_service(self, service): - self.log(f'Enabling service {service}', level=LOG_LEVELS.Info) - return self.arch_chroot(f'systemctl enable {service}').exit_code == 0 + def enable_service(self, *services): + for service in services: + self.log(f'Enabling service {service}', level=LOG_LEVELS.Info) + if (output := self.arch_chroot(f'systemctl enable {service}')).exit_code != 0: + raise ServiceException(f"Unable to start service {service}: {output}") def run_command(self, cmd, *args, **kwargs): - return sys_command(f'/usr/bin/arch-chroot {self.mountpoint} {cmd}') + return sys_command(f'/usr/bin/arch-chroot {self.target} {cmd}') def arch_chroot(self, cmd, *args, **kwargs): return self.run_command(cmd) @@ -223,15 +210,15 @@ class Installer(): conf = Networkd(Match={"Name": nic}, Network=network) - with open(f"{self.mountpoint}/etc/systemd/network/10-{nic}.network", "a") as netconf: + with open(f"{self.target}/etc/systemd/network/10-{nic}.network", "a") as netconf: netconf.write(str(conf)) def copy_ISO_network_config(self, enable_services=False): # Copy (if any) iwd password and config files if os.path.isdir('/var/lib/iwd/'): if (psk_files := glob.glob('/var/lib/iwd/*.psk')): - if not os.path.isdir(f"{self.mountpoint}/var/lib/iwd"): - os.makedirs(f"{self.mountpoint}/var/lib/iwd") + if not os.path.isdir(f"{self.target}/var/lib/iwd"): + os.makedirs(f"{self.target}/var/lib/iwd") if enable_services: # If we haven't installed the base yet (function called pre-maturely) @@ -251,43 +238,67 @@ class Installer(): self.enable_service('iwd') for psk in psk_files: - shutil.copy2(psk, f"{self.mountpoint}/var/lib/iwd/{os.path.basename(psk)}") + shutil.copy2(psk, f"{self.target}/var/lib/iwd/{os.path.basename(psk)}") # Copy (if any) systemd-networkd config files if (netconfigurations := glob.glob('/etc/systemd/network/*')): - if not os.path.isdir(f"{self.mountpoint}/etc/systemd/network/"): - os.makedirs(f"{self.mountpoint}/etc/systemd/network/") + if not os.path.isdir(f"{self.target}/etc/systemd/network/"): + os.makedirs(f"{self.target}/etc/systemd/network/") for netconf_file in netconfigurations: - shutil.copy2(netconf_file, f"{self.mountpoint}/etc/systemd/network/{os.path.basename(netconf_file)}") + shutil.copy2(netconf_file, f"{self.target}/etc/systemd/network/{os.path.basename(netconf_file)}") if enable_services: # If we haven't installed the base yet (function called pre-maturely) if self.helper_flags.get('base', False) is False: def post_install_enable_networkd_resolved(*args, **kwargs): - self.enable_service('systemd-networkd') - self.enable_service('systemd-resolved') - + self.enable_service('systemd-networkd', 'systemd-resolved') self.post_base_install.append(post_install_enable_networkd_resolved) # Otherwise, we can go ahead and enable the services else: - self.enable_service('systemd-networkd') - self.enable_service('systemd-resolved') + self.enable_service('systemd-networkd', 'systemd-resolved') + return True + def detect_encryption(self, partition): + if partition.encrypted: + return partition + elif partition.parent not in partition.path and Partition(partition.parent, None, autodetect_filesystem=True).filesystem == 'crypto_LUKS': + return Partition(partition.parent, None, autodetect_filesystem=True) + + return False + def minimal_installation(self): ## Add necessary packages if encrypting the drive ## (encrypted partitions default to btrfs for now, so we need btrfs-progs) ## TODO: Perhaps this should be living in the function which dictates ## the partitioning. Leaving here for now. - if self.partition.filesystem == 'btrfs': - #if self.partition.encrypted: - self.base_packages.append('btrfs-progs') - if self.partition.filesystem == 'xfs': - self.base_packages.append('xfsprogs') - if self.partition.filesystem == 'f2fs': - self.base_packages.append('f2fs-tools') + MODULES = [] + BINARIES = [] + FILES = [] + HOOKS = ["base", "udev", "autodetect", "keyboard", "keymap", "modconf", "block", "filesystems", "fsck"] + + for partition in self.partitions: + if partition.filesystem == 'btrfs': + #if partition.encrypted: + self.base_packages.append('btrfs-progs') + if partition.filesystem == 'xfs': + self.base_packages.append('xfsprogs') + if partition.filesystem == 'f2fs': + self.base_packages.append('f2fs-tools') + + # Configure mkinitcpio to handle some specific use cases. + if partition.filesystem == 'btrfs': + if 'btrfs' not in MODULES: + MODULES.append('btrfs') + if '/usr/bin/btrfs-progs' not in BINARIES: + BINARIES.append('/usr/bin/btrfs') + + if self.detect_encryption(partition): + if 'encrypt' not in HOOKS: + HOOKS.insert(HOOKS.index('filesystems'), 'encrypt') + self.pacstrap(self.base_packages) self.helper_flags['base-strapped'] = True #self.genfstab() @@ -298,39 +309,28 @@ class Installer(): elif vendor == "GenuineIntel": self.base_packages.append("intel-ucode") else: - self.log("unknown cpu vendor not installing ucode") - with open(f"{self.mountpoint}/etc/fstab", "a") as fstab: + self.log("Unknown cpu vendor not installing ucode") + with open(f"{self.target}/etc/fstab", "a") as fstab: fstab.write( "\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n" ) # Redundant \n at the start? who knows? ## TODO: Support locale and timezone - #os.remove(f'{self.mountpoint}/etc/localtime') - #sys_command(f'/usr/bin/arch-chroot {self.mountpoint} ln -s /usr/share/zoneinfo/{localtime} /etc/localtime') + #os.remove(f'{self.target}/etc/localtime') + #sys_command(f'/usr/bin/arch-chroot {self.target} ln -s /usr/share/zoneinfo/{localtime} /etc/localtime') #sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime') - self.set_hostname() + self.set_hostname('archinstall') self.set_locale('en_US') # TODO: Use python functions for this - sys_command(f'/usr/bin/arch-chroot {self.mountpoint} chmod 700 /root') - - # Configure mkinitcpio to handle some specific use cases. - # TODO: Yes, we should not overwrite the entire thing, but for now this should be fine - # since we just installed the base system. - if self.partition.filesystem == 'btrfs': - with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit: - mkinit.write('MODULES=(btrfs)\n') - mkinit.write('BINARIES=(/usr/bin/btrfs)\n') - mkinit.write('FILES=()\n') - mkinit.write('HOOKS=(base udev autodetect keyboard keymap modconf block encrypt filesystems fsck)\n') - sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux') - elif self.partition.encrypted: - with open(f'{self.mountpoint}/etc/mkinitcpio.conf', 'w') as mkinit: - mkinit.write('MODULES=()\n') - mkinit.write('BINARIES=()\n') - mkinit.write('FILES=()\n') - mkinit.write('HOOKS=(base udev autodetect keyboard keymap modconf block encrypt filesystems fsck)\n') - sys_command(f'/usr/bin/arch-chroot {self.mountpoint} mkinitcpio -p linux') + sys_command(f'/usr/bin/arch-chroot {self.target} chmod 700 /root') + + with open(f'{self.target}/etc/mkinitcpio.conf', 'w') as mkinit: + mkinit.write(f"MODULES=({' '.join(MODULES)})\n") + mkinit.write(f"BINARIES=({' '.join(BINARIES)})\n") + mkinit.write(f"FILES=({' '.join(FILES)})\n") + mkinit.write(f"HOOKS=({' '.join(HOOKS)})\n") + sys_command(f'/usr/bin/arch-chroot {self.target} mkinitcpio -p linux') self.helper_flags['base'] = True @@ -342,7 +342,15 @@ class Installer(): return True def add_bootloader(self, bootloader='systemd-bootctl'): - self.log(f'Adding bootloader {bootloader} to {self.boot_partition}', level=LOG_LEVELS.Info) + boot_partition = None + root_partition = None + for partition in self.partitions: + if partition.mountpoint == self.target+'/boot': + boot_partition = partition + elif partition.mountpoint == self.target: + root_partition = partition + + self.log(f'Adding bootloader {bootloader} to {boot_partition}', level=LOG_LEVELS.Info) if bootloader == 'systemd-bootctl': if not hasUEFI(): @@ -352,11 +360,11 @@ class Installer(): # And in which case we should do some clean up. # Install the boot loader - sys_command(f'/usr/bin/arch-chroot {self.mountpoint} bootctl --no-variables --path=/boot install') + sys_command(f'/usr/bin/arch-chroot {self.target} bootctl --no-variables --path=/boot install') # Modify or create a loader.conf - if os.path.isfile(f'{self.mountpoint}/boot/loader/loader.conf'): - with open(f'{self.mountpoint}/boot/loader/loader.conf', 'r') as loader: + 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 = [ @@ -364,7 +372,7 @@ class Installer(): f"timeout 5" ] - with open(f'{self.mountpoint}/boot/loader/loader.conf', 'w') as loader: + 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}\n') @@ -375,7 +383,7 @@ class Installer(): ## And blkid is wrong in terms of LUKS. #UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip() # Setup the loader entry - with open(f'{self.mountpoint}/boot/loader/entries/{self.init_time}.conf', 'w') as entry: + with open(f'{self.target}/boot/loader/entries/{self.init_time}.conf', 'w') as entry: entry.write(f'# Created by: archinstall\n') entry.write(f'# Created on: {self.init_time}\n') entry.write(f'title Arch Linux\n') @@ -393,37 +401,28 @@ class Installer(): ## so we'll use the old manual method until we get that sorted out. - if self.partition.encrypted: - log(f"Identifying root partition by DISK-UUID on {self.partition}, looking for '{os.path.basename(self.partition.real_device)}'.", level=LOG_LEVELS.Debug) - for root, folders, uids in os.walk('/dev/disk/by-uuid'): - for uid in uids: - real_path = os.path.realpath(os.path.join(root, uid)) - - log(f"Checking root partition match {os.path.basename(real_path)} against {os.path.basename(self.partition.real_device)}: {os.path.basename(real_path) == os.path.basename(self.partition.real_device)}", level=LOG_LEVELS.Debug) - if not os.path.basename(real_path) == os.path.basename(self.partition.real_device): continue - - entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n') - - self.helper_flags['bootloader'] = bootloader - return True - break + 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=LOG_LEVELS.Debug) + entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n') else: - log(f"Identifying root partition by PART-UUID on {self.partition}, looking for '{os.path.basename(self.partition.path)}'.", level=LOG_LEVELS.Debug) - entry.write(f'options root=PARTUUID={self.partition.uuid} rw intel_pstate=no_hwp\n') + log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=LOG_LEVELS.Debug) + entry.write(f'options root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp\n') - self.helper_flags['bootloader'] = bootloader - return True + self.helper_flags['bootloader'] = bootloader + return True - raise RequirementError(f"Could not identify the UUID of {self.partition}, there for {self.mountpoint}/boot/loader/entries/arch.conf will be broken until fixed.") + raise RequirementError(f"Could not identify the UUID of {self.partition}, there for {self.target}/boot/loader/entries/arch.conf will be broken until fixed.") elif bootloader == "grub-install": if hasUEFI(): - o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB')) + o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB')) sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg') else: - root_device = subprocess.check_output(f'basename "$(readlink -f "/sys/class/block/{self.partition.path.strip("/dev/")}/..")',shell=True).decode().strip() + root_device = subprocess.check_output(f'basename "$(readlink -f "/sys/class/block/{root_partition.path.strip("/dev/")}/..")', shell=True).decode().strip() if root_device == "block": - root_device = f"{self.partition.path}" - o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} grub-install --target=--target=i386-pc /dev/{root_device}')) + root_device = f"{root_partition.path}" + o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} grub-install --target=--target=i386-pc /dev/{root_device}')) sys_command('/usr/bin/arch-chroot grub-mkconfig -o /boot/grub/grub.cfg') else: raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}") @@ -449,19 +448,19 @@ class Installer(): def enable_sudo(self, entity :str, group=False): self.log(f'Enabling sudo permissions for {entity}.', level=LOG_LEVELS.Info) - with open(f'{self.mountpoint}/etc/sudoers', 'a') as sudoers: + with open(f'{self.target}/etc/sudoers', 'a') as sudoers: sudoers.write(f'{"%" if group else ""}{entity} ALL=(ALL) ALL\n') return True def user_create(self, user :str, password=None, groups=[], sudo=False): self.log(f'Creating user {user}', level=LOG_LEVELS.Info) - o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} useradd -m -G wheel {user}')) + o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} useradd -m -G wheel {user}')) if password: self.user_set_pw(user, password) if groups: for group in groups: - o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} gpasswd -a {user} {group}')) + o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} gpasswd -a {user} {group}')) if sudo and self.enable_sudo(user): self.helper_flags['user'] = True @@ -473,12 +472,12 @@ class Installer(): # This means the root account isn't locked/disabled with * in /etc/passwd self.helper_flags['user'] = True - o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.mountpoint} sh -c \"echo '{user}:{password}' | chpasswd\"")) + o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.target} sh -c \"echo '{user}:{password}' | chpasswd\"")) pass def set_keyboard_language(self, language): if len(language.strip()): - with open(f'{self.mountpoint}/etc/vconsole.conf', 'w') as vconsole: + with open(f'{self.target}/etc/vconsole.conf', 'w') as vconsole: vconsole.write(f'KEYMAP={language}\n') vconsole.write(f'FONT=lat9w-16\n') return True diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index a1d42196..ca077b3d 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -1,5 +1,7 @@ import os import shlex +import time +import pathlib from .exceptions import * from .general import * from .disk import Partition @@ -114,7 +116,7 @@ class luks2(): def unlock(self, partition, mountpoint, key_file): """ - Mounts a lukts2 compatible partition to a certain mountpoint. + Mounts a luks2 compatible partition to a certain mountpoint. Keyfile must be specified as there's no way to interact with the pw-prompt atm. :param mountpoint: The name without absolute path, for instance "luksdev" will point to /dev/mapper/luksdev @@ -123,6 +125,11 @@ class luks2(): from .disk import get_filesystem_type if '/' in mountpoint: os.path.basename(mountpoint) # TODO: Raise exception instead? + + wait_timer = time.time() + while pathlib.Path(partition.path).exists() is False and time.time() - wait_timer < 10: + time.sleep(0.025) + sys_command(f'/usr/bin/cryptsetup open {partition.path} {mountpoint} --key-file {os.path.abspath(key_file)} --type luks2') if os.path.islink(f'/dev/mapper/{mountpoint}'): self.mapdev = f'/dev/mapper/{mountpoint}' diff --git a/archinstall/lib/profiles.py b/archinstall/lib/profiles.py index 7e76c891..94d9f6ee 100644 --- a/archinstall/lib/profiles.py +++ b/archinstall/lib/profiles.py @@ -177,6 +177,52 @@ class Profile(Script): if hasattr(imported, '_prep_function'): return True return False + """ + def has_post_install(self): + with open(self.path, 'r') as source: + source_data = source.read() + + # Some crude safety checks, make sure the imported profile has + # a __name__ check and if so, check if it's got a _prep_function() + # we can call to ask for more user input. + # + # If the requirements are met, import with .py in the namespace to not + # trigger a traditional: + # if __name__ == 'moduleName' + if '__name__' in source_data and '_post_install' in source_data: + with self.load_instructions(namespace=f"{self.namespace}.py") as imported: + if hasattr(imported, '_post_install'): + return True + """ + + def is_top_level_profile(self): + with open(self.path, 'r') as source: + source_data = source.read() + + # TODO: I imagine that there is probably a better way to write this. + return 'top_level_profile = True' in source_data + + @property + def packages(self) -> list: + """ + Returns a list of packages baked into the profile definition. + If no package definition has been done, .packages() will return None. + """ + with open(self.path, 'r') as source: + source_data = source.read() + + # Some crude safety checks, make sure the imported profile has + # a __name__ check before importing. + # + # If the requirements are met, import with .py in the namespace to not + # trigger a traditional: + # if __name__ == 'moduleName' + if '__name__' in source_data and '__packages__' in source_data: + with self.load_instructions(namespace=f"{self.namespace}.py") as imported: + if hasattr(imported, '__packages__'): + return imported.__packages__ + return None + def has_post_install(self): with open(self.path, 'r') as source: diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index d17691de..425dc9a9 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -1,4 +1,5 @@ import getpass, pathlib, os, shutil, re +import sys, time, signal from .exceptions import * from .profiles import Profile from .locale_helpers import search_keyboard_layout @@ -21,14 +22,49 @@ def get_longest_option(options): return max([len(x) for x in options]) def check_for_correct_username(username): - if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32: - return True - log( + if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32: + return True + log( "The username you entered is invalid. Try again", level=LOG_LEVELS.Warning, fg='red' ) - return False + return False + +def do_countdown(): + SIG_TRIGGER = False + def kill_handler(sig, frame): + print() + exit(0) + + def sig_handler(sig, frame): + global SIG_TRIGGER + SIG_TRIGGER = True + signal.signal(signal.SIGINT, kill_handler) + + original_sigint_handler = signal.getsignal(signal.SIGINT) + signal.signal(signal.SIGINT, sig_handler) + + for i in range(5, 0, -1): + print(f"{i}", end='') + + for x in range(4): + sys.stdout.flush() + time.sleep(0.25) + print(".", end='') + + if SIG_TRIGGER: + abort = input('\nDo you really want to abort (y/n)? ') + if abort.strip() != 'n': + exit(0) + + if SIG_TRIGGER is False: + sys.stdin.read() + SIG_TRIGGER = False + signal.signal(signal.SIGINT, sig_handler) + print() + signal.signal(signal.SIGINT, original_sigint_handler) + return True def get_password(prompt="Enter a password: "): while (passwd := getpass.getpass(prompt)): @@ -196,7 +232,7 @@ def generic_select(options, input_text="Select one of the above by index or abso return None elif selected_option.isdigit(): selected_option = int(selected_option) - if selected_option >= len(options): + if selected_option > len(options): raise RequirementError(f'Selected option "{selected_option}" is out of range') selected_option = options[selected_option] elif selected_option in options: @@ -221,8 +257,10 @@ def select_disk(dict_o_disks): if len(drives) >= 1: for index, drive in enumerate(drives): print(f"{index}: {drive} ({dict_o_disks[drive]['size'], dict_o_disks[drive].device, dict_o_disks[drive]['label']})") - drive = input('Select one of the above disks (by number or full path): ') - if drive.isdigit(): + drive = input('Select one of the above disks (by number or full path) or write /mnt to skip partitioning: ') + if drive.strip() == '/mnt': + return None + elif drive.isdigit(): drive = int(drive) if drive >= len(drives): raise DiskError(f'Selected option "{drive}" is out of range') |