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:
authorAnton Hvornum <anton.feeds@gmail.com>2021-05-24 12:34:32 +0200
committerAnton Hvornum <anton.feeds@gmail.com>2021-05-24 12:34:32 +0200
commit9b0de26c67b6bb90b69d5b4ef874dc0bed407a77 (patch)
tree69f1b5e0b6ad48018ce9e18d6d74079bfb123540 /archinstall/lib/installer.py
parent404197dc93c2efb24097772848af708d833bdd98 (diff)
parent7daaf1143fca2723bfb63ab4e3030485446da1c0 (diff)
Syncing in latest changes from master
Diffstat (limited to 'archinstall/lib/installer.py')
-rw-r--r--archinstall/lib/installer.py339
1 files changed, 194 insertions, 145 deletions
diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py
index 51539d2c..dff05b0a 100644
--- a/archinstall/lib/installer.py
+++ b/archinstall/lib/installer.py
@@ -1,54 +1,54 @@
-import os, stat, time, shutil, pathlib
-import subprocess, logging
-from .exceptions import *
from .disk import *
-from .general import *
-from .user_interaction import *
-from .profiles import Profile
+from .hardware import *
+from .locale_helpers import verify_keyboard_layout, verify_x11_keyboard_layout
from .mirrors import *
-from .systemd import Networkd
-from .output import log
from .storage import storage
-from .hardware import *
from .plugins import plugins
+from .user_interaction import *
# Any package that the Installer() is responsible for (optional and the default ones)
__packages__ = ["base", "base-devel", "linux-firmware", "linux", "linux-lts", "linux-zen", "linux-hardened"]
-class Installer():
+
+class Installer:
"""
`Installer()` is the wrapper for most basic installation steps.
It also wraps :py:func:`~archinstall.Installer.pacstrap` among other things.
:param partition: Requires a partition as the first argument, this is
- so that the installer can mount to `mountpoint` and strap packages there.
+ so that the installer can mount to `mountpoint` and strap packages there.
:type partition: class:`archinstall.Partition`
:param boot_partition: There's two reasons for needing a boot partition argument,
- The first being so that `mkinitcpio` can place the `vmlinuz` kernel at the right place
- during the `pacstrap` or `linux` and the base packages for a minimal installation.
- The second being when :py:func:`~archinstall.Installer.add_bootloader` is called,
- A `boot_partition` must be known to the installer before this is called.
+ The first being so that `mkinitcpio` can place the `vmlinuz` kernel at the right place
+ during the `pacstrap` or `linux` and the base packages for a minimal installation.
+ The second being when :py:func:`~archinstall.Installer.add_bootloader` is called,
+ A `boot_partition` must be known to the installer before this is called.
:type boot_partition: class:`archinstall.Partition`
:param profile: A profile to install, this is optional and can be called later manually.
- This just simplifies the process by not having to call :py:func:`~archinstall.Installer.install_profile` later on.
+ This just simplifies the process by not having to call :py:func:`~archinstall.Installer.install_profile` later on.
:type profile: str, optional
:param hostname: The given /etc/hostname for the machine.
:type hostname: str, optional
"""
- def __init__(self, target, *, base_packages=__packages__[:3], kernels=['linux']):
+
+ def __init__(self, target, *, base_packages=None, kernels=None):
+ if base_packages is None:
+ base_packages = __packages__[:3]
+ if kernels is None:
+ kernels = ['linux']
self.target = target
self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
self.milliseconds = int(str(time.time()).split('.')[1])
self.helper_flags = {
- 'base' : False,
- 'bootloader' : False
+ 'base': False,
+ 'bootloader': False
}
-
+
self.base_packages = base_packages.split(' ') if type(base_packages) is str else base_packages
for kernel in kernels:
self.base_packages.append(kernel)
@@ -58,6 +58,12 @@ class Installer():
storage['session'] = self
self.partitions = get_partitions_in_use(self.target)
+ self.MODULES = []
+ self.BINARIES = []
+ self.FILES = []
+ self.HOOKS = ["base", "udev", "autodetect", "keyboard", "keymap", "modconf", "block", "filesystems", "fsck"]
+ self.KERNEL_PARAMS = []
+
def log(self, *args, level=logging.DEBUG, **kwargs):
"""
installer.log() wraps output.log() mainly to set a default log-level for this install session.
@@ -69,11 +75,10 @@ class Installer():
return self
def __exit__(self, *args, **kwargs):
- # b''.join(sys_command(f'sync')) # No need to, since the underlying fs() object will call sync.
+ # b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync.
# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager
if len(args) >= 2 and args[1]:
- #self.log(self.trace_log.decode('UTF-8'), level=logging.DEBUG)
self.log(args[1], level=logging.ERROR, fg='red')
self.sync_log_to_install_medium()
@@ -81,7 +86,7 @@ class Installer():
# We avoid printing /mnt/<log path> because that might confuse people if they note it down
# and then reboot, and a identical log file will be found in the ISO medium anyway.
print(f"[!] A log file has been created here: {os.path.join(storage['LOG_PATH'], storage['LOG_FILE'])}")
- print(f" Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues")
+ print(" Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues")
raise args[1]
self.genfstab()
@@ -95,10 +100,10 @@ class Installer():
self.log('Some required steps were not successfully installed/configured before leaving the installer:', fg='red', level=logging.WARNING)
for step in missing_steps:
self.log(f' - {step}', fg='red', level=logging.WARNING)
-
+
self.log(f"Detailed error logs can be found at: {storage['LOG_PATH']}", level=logging.WARNING)
- self.log(f"Submit this zip file as an issue to https://github.com/archlinux/archinstall/issues", level=logging.WARNING)
-
+ self.log("Submit this zip file as an issue to https://github.com/archlinux/archinstall/issues", level=logging.WARNING)
+
self.sync_log_to_install_medium()
return False
@@ -106,12 +111,12 @@ class Installer():
# Copy over the install log (if there is one) to the install medium if
# at least the base has been strapped in, otherwise we won't have a filesystem/structure to copy to.
if self.helper_flags.get('base-strapped', False) is True:
- if (filename := storage.get('LOG_FILE', None)):
+ if filename := storage.get('LOG_FILE', None):
absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename)
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.target}/{absolute_logfile}")
return True
@@ -119,14 +124,15 @@ class Installer():
def mount(self, partition, mountpoint, create_mountpoint=True):
if create_mountpoint and not os.path.isdir(f'{self.target}{mountpoint}'):
os.makedirs(f'{self.target}{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]
def pacstrap(self, *packages, **kwargs):
- if type(packages[0]) in (list, tuple): packages = packages[0]
+ if type(packages[0]) in (list, tuple):
+ packages = packages[0]
for plugin in plugins.values():
if hasattr(plugin, 'on_pacstrap'):
@@ -135,8 +141,8 @@ class Installer():
self.log(f'Installing packages: {packages}', level=logging.INFO)
- if (sync_mirrors := sys_command('/usr/bin/pacman -Syy')).exit_code == 0:
- if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.target} {" ".join(packages)}', **kwargs)).exit_code == 0:
+ if (sync_mirrors := SysCommand('/usr/bin/pacman -Syy')).exit_code == 0:
+ if (pacstrap := SysCommand(f'/usr/bin/pacstrap {self.target} {" ".join(packages)}', peak_output=True)).exit_code == 0:
return True
else:
self.log(f'Could not strap in packages: {pacstrap.exit_code}', level=logging.INFO)
@@ -148,37 +154,39 @@ class Installer():
def genfstab(self, flags='-pU'):
self.log(f"Updating {self.target}/etc/fstab", level=logging.INFO)
-
- 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)
+
+ with open(f"{self.target}/etc/fstab", 'a') as fstab_fh:
+ fstab_fh.write(SysCommand(f'/usr/bin/genfstab {flags} {self.target}').decode())
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 :str, *args, **kwargs):
+ 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
+ if not len(locale):
+ return True
with open(f'{self.target}/etc/locale.gen', 'a') as fh:
fh.write(f'{locale}.{encoding} {encoding}\n')
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.target} locale-gen').exit_code == 0 else False
+ return True if SysCommand(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 not zone:
+ return True
+ if not len(zone):
+ return True # Redundant
- if (pathlib.Path("/usr")/"share"/"zoneinfo"/zone).exists():
- (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')
+ if (pathlib.Path("/usr") / "share" / "zoneinfo" / zone).exists():
+ (pathlib.Path(self.target) / "etc" / "localtime").unlink(missing_ok=True)
+ SysCommand(f'/usr/bin/arch-chroot {self.target} ln -s /usr/share/zoneinfo/{zone} /etc/localtime')
return True
else:
self.log(
@@ -188,7 +196,7 @@ class Installer():
)
def activate_ntp(self):
- self.log(f'Installing and activating NTP.', level=logging.INFO)
+ self.log('Installing and activating NTP.', level=logging.INFO)
if self.pacstrap('ntp'):
if self.enable_service('ntpd'):
return True
@@ -200,18 +208,20 @@ class Installer():
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.target} {cmd}')
+ return SysCommand(f'/usr/bin/arch-chroot {self.target} {cmd}')
def arch_chroot(self, cmd, *args, **kwargs):
if 'runas' in kwargs:
cmd = f"su - {kwargs['runas']} -c \"{cmd}\""
-
+
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):
+ from .systemd import Networkd
+
if dhcp:
conf = Networkd(Match={"Name": nic}, Network={"DHCP": "yes"})
else:
@@ -225,14 +235,14 @@ class Installer():
network["DNS"] = dns
conf = Networkd(Match={"Name": nic}, Network=network)
-
+
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):
+ 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 psk_files := glob.glob('/var/lib/iwd/*.psk'):
if not os.path.isdir(f"{self.target}/var/lib/iwd"):
os.makedirs(f"{self.target}/var/lib/iwd")
@@ -240,6 +250,7 @@ class Installer():
# If we haven't installed the base yet (function called pre-maturely)
if self.helper_flags.get('base', False) is False:
self.base_packages.append('iwd')
+
# This function will be called after minimal_installation()
# as a hook for post-installs. This hook is only needed if
# base is not installed yet.
@@ -257,7 +268,7 @@ class Installer():
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 netconfigurations := glob.glob('/etc/systemd/network/*'):
if not os.path.isdir(f"{self.target}/etc/systemd/network/"):
os.makedirs(f"{self.target}/etc/systemd/network/")
@@ -267,38 +278,43 @@ class Installer():
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', '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', 'systemd-resolved')
-
return True
def detect_encryption(self, partition):
+ part = Partition(partition.parent, None, autodetect_filesystem=True)
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)
-
+ elif partition.parent not in partition.path and part.filesystem == 'crypto_LUKS':
+ return part
+
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.
+ def mkinitcpio(self, *flags):
+ 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")
+ mkinit.write(f"HOOKS=({' '.join(self.HOOKS)})\n")
+ SysCommand(f'/usr/bin/arch-chroot {self.target} mkinitcpio {" ".join(flags)}')
- MODULES = []
- BINARIES = []
- FILES = []
- HOOKS = ["base", "udev", "autodetect", "keyboard", "keymap", "modconf", "block", "filesystems", "fsck"]
+ 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.
for partition in self.partitions:
if partition.filesystem == 'btrfs':
- #if partition.encrypted:
+ # if partition.encrypted:
self.base_packages.append('btrfs-progs')
if partition.filesystem == 'xfs':
self.base_packages.append('xfsprogs')
@@ -307,50 +323,48 @@ class Installer():
# 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 'btrfs' not in self.MODULES:
+ self.MODULES.append('btrfs')
+ if '/usr/bin/btrfs-progs' not in self.BINARIES:
+ self.BINARIES.append('/usr/bin/btrfs')
if self.detect_encryption(partition):
- if 'encrypt' not in HOOKS:
- HOOKS.insert(HOOKS.index('filesystems'), 'encrypt')
+ if 'encrypt' not in self.HOOKS:
+ self.HOOKS.insert(self.HOOKS.index('filesystems'), 'encrypt')
- if not(hasUEFI()): # TODO: Allow for grub even on EFI
+ if not has_uefi():
self.base_packages.append('grub')
-
- self.pacstrap(self.base_packages)
- self.helper_flags['base-strapped'] = True
- #self.genfstab()
- if not isVM():
- vendor = cpuVendor()
- if vendor == "AuthenticAMD":
+
+ if not is_vm():
+ vendor = cpu_vendor()
+ if vendor == "AuthenticAMD":
self.base_packages.append("amd-ucode")
+ if (ucode := pathlib.Path(f"{self.target}/boot/amd-ucode.img")).exists():
+ ucode.unlink()
elif vendor == "GenuineIntel":
self.base_packages.append("intel-ucode")
+ if (ucode := pathlib.Path(f"{self.target}/boot/intel-ucode.img")).exists():
+ ucode.unlink()
else:
- self.log("Unknown cpu vendor not installing ucode")
+ self.log(f"Unknown CPU vendor '{vendor}' detected. Archinstall won't install any ucode.", level=logging.DEBUG)
+
+ self.pacstrap(self.base_packages)
+ self.helper_flags['base-strapped'] = True
+
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.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')
+ 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.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('archinstall')
self.set_locale('en_US')
# TODO: Use python functions for this
- sys_command(f'/usr/bin/arch-chroot {self.target} chmod 700 /root')
+ SysCommand(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')
+ self.mkinitcpio('-P')
self.helper_flags['base'] = True
@@ -365,7 +379,7 @@ class Installer():
boot_partition = None
root_partition = None
for partition in self.partitions:
- if partition.mountpoint == self.target+'/boot':
+ if partition.mountpoint == self.target + '/boot':
boot_partition = partition
elif partition.mountpoint == self.target:
root_partition = partition
@@ -375,14 +389,16 @@ class Installer():
if bootloader == 'systemd-bootctl':
self.pacstrap('efibootmgr')
- if not hasUEFI():
+ if not has_uefi():
raise HardwareIncompatibilityError
# TODO: Ideally we would want to check if another config
# points towards the same disk and/or partition.
# And in which case we should do some clean up.
# Install the boot loader
- sys_command(f'/usr/bin/arch-chroot {self.target} bootctl --no-variables --path=/boot install')
+ if SysCommand(f'/usr/bin/arch-chroot {self.target} bootctl --path=/boot install').exit_code != 0:
+ # Fallback, try creating the boot loader without touching the EFI variables
+ SysCommand(f'/usr/bin/arch-chroot {self.target} bootctl --no-variables --path=/boot install')
# Modify or create a loader.conf
if os.path.isfile(f'{self.target}/boot/loader/loader.conf'):
@@ -391,65 +407,68 @@ class Installer():
else:
loader_data = [
f"default {self.init_time}",
- f"timeout 5"
+ "timeout 5"
]
-
+
with open(f'{self.target}/boot/loader/loader.conf', 'w') as loader:
for line in loader_data:
if line[:8] == 'default ':
loader.write(f'default {self.init_time}\n')
+ elif line[:8] == '#timeout' and 'timeout 5' not in loader_data:
+ # We add in the default timeout to support dual-boot
+ loader.write(f"{line[1:]}\n")
else:
- loader.write(f"{line}")
+ loader.write(f"{line}\n")
- ## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
- ## 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()
+ # For some reason, blkid and /dev/disk/by-uuid are not getting along well.
+ # 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.target}/boot/loader/entries/{self.init_time}.conf', 'w') as entry:
- entry.write(f'# Created by: archinstall\n')
+ entry.write('# Created by: archinstall\n')
entry.write(f'# Created on: {self.init_time}\n')
- entry.write(f'title Arch Linux\n')
- entry.write(f'linux /vmlinuz-linux\n')
- if not isVM():
- vendor = cpuVendor()
- if vendor == "AuthenticAMD":
+ entry.write('title Arch Linux\n')
+ entry.write('linux /vmlinuz-linux\n')
+ if not is_vm():
+ vendor = cpu_vendor()
+ if vendor == "AuthenticAMD":
entry.write("initrd /amd-ucode.img\n")
elif vendor == "GenuineIntel":
entry.write("initrd /intel-ucode.img\n")
else:
self.log("unknow cpu vendor, not adding ucode to systemd-boot config")
- entry.write(f'initrd /initramfs-linux.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.
-
+ entry.write('initrd /initramfs-linux.img\n')
+ # blkid doesn't trigger on loopback devices really well,
+ # so we'll use the old manual method until we get that sorted out.
- if (real_device := self.detect_encryption(root_partition)):
+ if real_device := self.detect_encryption(root_partition):
# TODO: We need to detect if the encrypted device is a whole disk encryption,
# or simply a partition encryption. Right now we assume it's a partition (and we always have)
log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=logging.DEBUG)
- entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
+ entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp {" ".join(self.KERNEL_PARAMS)}\n')
else:
log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=logging.DEBUG)
- entry.write(f'options root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp\n')
+ entry.write(f'options root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp {" ".join(self.KERNEL_PARAMS)}\n')
self.helper_flags['bootloader'] = bootloader
return True
- 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":
self.pacstrap('grub')
- if hasUEFI():
- 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 /mnt grub-mkconfig -o /boot/grub/grub.cfg')
+ if has_uefi():
+ self.pacstrap('efibootmgr')
+ o = b''.join(SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB'))
+ SysCommand('/usr/bin/arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg')
+ self.helper_flags['bootloader'] = True
return True
else:
- root_device = subprocess.check_output(f'basename "$(readlink -f /sys/class/block/{root_partition.path.replace("/dev/","")}/..)"', shell=True).decode().strip()
+ root_device = subprocess.check_output(f'basename "$(readlink -f /sys/class/block/{root_partition.path.replace("/dev/", "")}/..)"', shell=True).decode().strip()
if root_device == "block":
root_device = f"{root_partition.path}"
- o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} grub-install --target=i386-pc /dev/{root_device}'))
- sys_command('/usr/bin/arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg')
- self.helper_flags['bootloader'] = bootloader
+ o = b''.join(SysCommand(f'/usr/bin/arch-chroot {self.target} grub-install --target=i386-pc /dev/{root_device}'))
+ SysCommand('/usr/bin/arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg')
+ self.helper_flags['bootloader'] = True
return True
else:
raise RequirementError(f"Unknown (or not yet implemented) bootloader requested: {bootloader}")
@@ -458,14 +477,7 @@ class Installer():
return self.pacstrap(*packages)
def install_profile(self, profile):
- # TODO: Replace this with a import archinstall.session instead in the profiles.
- # The tricky thing with doing the import archinstall.session instead is that
- # profiles might be run from a different chroot, and there's no way we can
- # guarantee file-path safety when accessing the installer object that way.
- # Doing the __builtins__ replacement, ensures that the global variable "installation"
- # is always kept up to date. It's considered a nasty hack - but it's a safe way
- # of ensuring 100% accuracy of archinstall session variables.
- __builtins__['installation'] = self
+ storage['installation_session'] = self
if type(profile) == str:
profile = Profile(self, profile)
@@ -473,21 +485,23 @@ class Installer():
self.log(f'Installing network profile {profile}', level=logging.INFO)
return profile.install()
- def enable_sudo(self, entity :str, group=False):
+ def enable_sudo(self, entity: str, group=False):
self.log(f'Enabling sudo permissions for {entity}.', level=logging.INFO)
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):
+ def user_create(self, user: str, password=None, groups=None, sudo=False):
+ if groups is None:
+ groups = []
self.log(f'Creating user {user}', level=logging.INFO)
- o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.target} useradd -m -G wheel {user}'))
+ o = b''.join(SysCommand(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.target} gpasswd -a {user} {group}'))
+ o = b''.join(SysCommand(f'/usr/bin/arch-chroot {self.target} gpasswd -a {user} {group}'))
if sudo and self.enable_sudo(user):
self.helper_flags['user'] = True
@@ -499,20 +513,55 @@ 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.target} sh -c \"echo '{user}:{password}' | chpasswd\""))
+ o = b''.join(SysCommand(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=logging.INFO)
- o = b''.join(sys_command(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\""))
+ o = b''.join(SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c \"chsh -s {shell} {user}\""))
pass
- def set_keyboard_language(self, language):
+ def set_keyboard_language(self, language: str) -> bool:
+ if len(language.strip()):
+ if not verify_keyboard_layout(language):
+ self.log(f"Invalid keyboard language specified: {language}", fg="red", level=logging.ERROR)
+ return False
+
+ # In accordance with https://github.com/archlinux/archinstall/issues/107#issuecomment-841701968
+ # Setting an empty keymap first, allows the subsequent call to set layout for both console and x11.
+ from .systemd import Boot
+
+ with Boot(self) as session:
+ session.SysCommand(["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}")
+
+ self.log(f"Keyboard language for this installation is now set to: {language}")
+ else:
+ self.log('Keyboard language was not changed from default (no language specified).', fg="yellow", level=logging.INFO)
+
+ return True
+
+ def set_x11_keyboard_language(self, language: str) -> bool:
+ """
+ A fallback function to set x11 layout specifically and separately from console layout.
+ This isn't strictly necessary since .set_keyboard_language() does this as well.
+ """
if len(language.strip()):
- with open(f'{self.target}/etc/vconsole.conf', 'w') as vconsole:
- vconsole.write(f'KEYMAP={language}\n')
- vconsole.write(f'FONT=lat9w-16\n')
+ if not verify_x11_keyboard_layout(language):
+ self.log(f"Invalid x11-keyboard language specified: {language}", fg="red", level=logging.ERROR)
+ return False
+
+ from .systemd import Boot
+
+ 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}")
else:
- self.log(f'Keyboard language was not changed from default (no language specified).', fg="yellow", level=logging.INFO)
+ self.log(f'X11-Keyboard language was not changed from default (no language specified).', fg="yellow", level=logging.INFO)
+
return True