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.py243
1 files changed, 128 insertions, 115 deletions
diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py
index b7fe5e9d..758033a7 100644
--- a/archinstall/lib/installer.py
+++ b/archinstall/lib/installer.py
@@ -1,4 +1,4 @@
-import os, stat, time, shutil, pathlib
+import os, stat, time, shutil, pathlib, subprocess
from .exceptions import *
from .disk import *
@@ -34,30 +34,21 @@ class Installer():
:type hostname: str, optional
"""
- def __init__(self, partition, boot_partition, *, base_packages='base base-devel linux linux-firmware efibootmgr', 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):
"""
@@ -67,9 +58,6 @@ class Installer():
log(*args, level=level, **kwargs)
def __enter__(self, *args, **kwargs):
- 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):
@@ -93,6 +81,7 @@ class Installer():
if not (missing_steps := self.post_install_check()):
self.log('Installation completed without any errors. You may now reboot.', bg='black', fg='green', level=LOG_LEVELS.Info)
self.sync_log_to_install_medium()
+
return True
else:
self.log('Some required steps were not successfully installed/configured before leaving the installer:', bg='black', fg='red', level=LOG_LEVELS.Warning)
@@ -112,18 +101,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]
@@ -133,7 +122,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)}', **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)
@@ -141,42 +130,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(
@@ -191,16 +179,21 @@ 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)
+ def drop_to_shell(self):
+ subprocess.check_call(f"/usr/bin/arch-chroot {self.target}", shell=True)
+
def configure_nic(self, nic, dhcp=True, ip=None, gateway=None, dns=None, *args, **kwargs):
if dhcp:
conf = Networkd(Match={"Name": nic}, Network={"DHCP": "yes"})
@@ -216,15 +209,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)
@@ -244,79 +237,92 @@ 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()
- with open(f"{self.mountpoint}/etc/fstab", "a") as fstab:
+ 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
@@ -328,7 +334,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':
# TODO: Ideally we would want to check if another config
@@ -336,11 +350,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 = [
@@ -348,7 +362,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')
@@ -360,7 +374,7 @@ class Installer():
#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')
@@ -370,28 +384,19 @@ 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 {root_partition}, there for {self.target}/boot/loader/entries/arch.conf will be broken until fixed.")
else:
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
@@ -416,19 +421,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
@@ -440,12 +445,20 @@ 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 user_set_shell(self, user, shell):
+ self.log(f'Setting shell for {user} to {shell}', level=LOG_LEVELS.Info)
+
+ o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\""))
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')
+ else:
+ self.log(f'Keyboard language was not changed from default (no language specified).', fg="yellow", level=LOG_LEVELS.Info)
return True