Skip to content

Commit

Permalink
python: setup: arch: Switch to systemd-boot if necessary
Browse files Browse the repository at this point in the history
Signed-off-by: Nathan Chancellor <[email protected]>
  • Loading branch information
nathanchance committed Dec 2, 2024
1 parent 67bf716 commit 1b92018
Showing 1 changed file with 118 additions and 16 deletions.
134 changes: 118 additions & 16 deletions python/setup/arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Copyright (C) 2022-2023 Nathan Chancellor

from argparse import ArgumentParser
from collections import UserDict
import getpass
import os
from pathlib import Path
Expand All @@ -29,6 +30,27 @@
UCODE_VENDOR = 'intel'


class CmdlineOptions(UserDict):

def __init__(self, initial_argument):
if isinstance(initial_argument, str):
super().__init__()

for item in initial_argument.split(' '):
if item := item.strip():
if '=' in item:
key, value = item.split('=', maxsplit=1)
else:
key, value = item, None
self.data[key] = value
else:
super().__init__(initial_argument)

def __str__(self):
return ' '.join(
sorted(f"{key}={value}" if value else key for key, value in self.data.items()))


def add_mods_to_mkinitcpio(modules):
mkinitcpio_conf, conf_text = lib.utils.path_and_text('/etc/mkinitcpio.conf')

Expand Down Expand Up @@ -101,8 +123,10 @@ def configure_systemd_boot(init=True, conf='linux.conf'):
systemd_boot_update_hook.write_text(systemd_boot_update_hook_txt, encoding='utf-8')
systemd_boot_update_hook.chmod(0o644)

# If we already set up the configuration, no need to go through all this
# again, unless we are not doing the initial configuration
# If we already set up the configuration (either via this function or
# switch_to_systemd_boot(), depending on what setup was installed prior to
# running this setup), no need to go through all this again, unless we are
# not doing the initial configuration
if (linux_conf := (boot_entries := Path('/boot/loader/entries')) / conf).exists() and init:
return

Expand All @@ -119,23 +143,13 @@ def configure_systemd_boot(init=True, conf='linux.conf'):
linux_conf_text = linux_conf.read_text(encoding='utf-8')
if not (match := re.search('^options (.*)$', linux_conf_text, flags=re.M)):
raise RuntimeError(f"Could not find 'options' line in {linux_conf}?")
current_options_str = match.groups()[0]
current_options = {opt for elem in current_options_str.split(' ') if (opt := elem.strip())}
new_options = current_options.copy()

# Add 'console=' if necessary (when connected by serial console in a
# virtual machine)
if lib.setup.is_virtual_machine() and 'DISPLAY' not in os.environ:
new_options.add('console=ttyS0,115200n8')

# Enable the performance governor
new_options.add('cpufreq.default_governor=performance')
current_options = CmdlineOptions(match.groups()[0])

# Mitigate SMT RSB vulnerability
new_options.add('kvm.mitigate_smt_rsb=1')
new_options = current_options.copy()
new_options.update(get_cmdline_additions())

if current_options != new_options:
new_text = linux_conf_text.replace(current_options_str, ' '.join(sorted(new_options)))
new_text = linux_conf_text.replace(str(current_options), str(new_options))
linux_conf.write_text(new_text, encoding='utf-8')

# Ensure that the new configuration is the default on the machine.
Expand Down Expand Up @@ -189,6 +203,20 @@ def fix_fstab():
subprocess.run(['dos2unix', '/etc/fstab'], check=True)


def get_cmdline_additions():
options = CmdlineOptions({
# Enable the performance governor
'cpufreq.default_governor': 'performance',
# Mitigate SMT RSB vulnerability
'kvm.mitigate_smt_rsb': '1',
})
# Add 'console=' if necessary (when connected by serial console in a
# virtual machine)
if lib.setup.is_virtual_machine() and 'DISPLAY' not in os.environ:
options['console'] = 'ttyS0,115200n8'
return options


def pacman_install(subargs):
lib.setup.pacman(['-S', '--noconfirm', *subargs])

Expand Down Expand Up @@ -465,6 +493,79 @@ def setup_user(username, userpass):
lib.setup.setup_ssh_authorized_keys(username)


def switch_to_systemd_boot(dryrun=False):
# If we are not booted in UEFI mode, we cannot switch to systemd-boot
if not Path('/sys/firmware/efi').exists():
return

# If systemd-boot is already installed, we are good to go
if lib.setup.using_systemd_boot():
return

# Install systemd-boot to ESP
if dryrun:
print('$ bootctl install')
else:
subprocess.run(['bootctl', 'install'], check=True)

# Create initial loader.conf
loader_conf_txt = 'default linux.conf\ntimeout 3\n'
if dryrun:
print('$ mkdir /boot/loader')
print(f"$ echo '{loader_conf_txt}' > /boot/loader/loader.conf")
else:
Path('/boot/loader').mkdir(exist_ok=True)
Path('/boot/loader/loader.conf').write_text(loader_conf_txt, encoding='utf-8')

# Get partition UUID and filesystem type of /
partuuid, fstype = subprocess.run(['findmnt', '-n', '-o', 'PARTUUID,FSTYPE', '/'],
capture_output=True,
check=True,
text=True).stdout.strip().split(' ')

# Default cmdline options
cmdline_options = CmdlineOptions({
'root': f"PARTUUID={partuuid}",
'rootfstype': fstype,
'rw': None,
})
cmdline_options.update(get_cmdline_additions())
# Copy over any cmdline options that we added in grub, as those might be
# necessary for the machine to work properly.
if (grub_default := Path('/etc/default/grub')).exists():
grub_default_txt = grub_default.read_text(encoding='utf-8')
if match := re.search('^GRUB_CMDLINE_LINUX_DEFAULT="(.*)"$', grub_default_txt, flags=re.M):
default_skip = (
'loglevel=', # I do not mind seeing logs
'quiet', # I do not mind the boot up noise
)
filtered_str = ' '.join(item for item in match.groups()[0].split(' ')
if not item.startswith(default_skip))
cmdline_options.update(CmdlineOptions(filtered_str))
if match := re.search('^GRUB_CMDLINE_LINUX="(.*)"$', grub_default_txt, flags=re.M):
cmdline_options.update(CmdlineOptions(match.groups()[0]))

# Create initial linux.conf
linux_conf_txt = (
'title Arch Linux (linux)\n'
'linux /vmlinuz-linux\n'
f"initrd /{UCODE_VENDOR}-ucode.img\n" if UCODE_VENDOR else '',
'initrd /initramfs-linux.img\n'
f"options {' '.join(sorted(cmdline_options))}\n",
)
if dryrun:
print('$ mkdir /boot/loader/entries')
print(f"$ echo '{linux_conf_txt}' > /boot/loader/entries/linux.conf")
else:
Path('/boot/loader/entries').mkdir(exist_ok=True)
Path('/boot/loader/entries/linux.conf').write_text(linux_conf_txt, encoding='utf-8')

# Remove grub
lib.setup.remove_if_installed('grub')
if (boot_grub := Path('/boot/grub')).exists():
shutil.rmtree(boot_grub)


def uncomment_pacman_option(conf, option, old_value=None, new_value=None):
if old_value and new_value:
return re.sub(f"^#{option} = {old_value}", f"{option} = {new_value}", conf, flags=re.M)
Expand Down Expand Up @@ -502,6 +603,7 @@ def vmware_adjustments():
password = getpass.getpass(prompt='Password for Arch Linux user account: ')

prechecks()
switch_to_systemd_boot()
configure_systemd_boot()
pacman_settings()
pacman_key_setup()
Expand Down

0 comments on commit 1b92018

Please sign in to comment.