Update ruff formatter (#3496)

* Update ruff formatter

* Update
This commit is contained in:
Daniel Girtler 2025-05-24 17:58:42 +10:00 committed by GitHub
parent 9af23218c4
commit d3f32f308c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
114 changed files with 2932 additions and 2930 deletions

View File

@ -25,36 +25,36 @@ def plugin(f, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
def _log_sys_info() -> None: def _log_sys_info() -> None:
# Log various information about hardware before starting the installation. This might assist in troubleshooting # Log various information about hardware before starting the installation. This might assist in troubleshooting
debug(f"Hardware model detected: {SysInfo.sys_vendor()} {SysInfo.product_name()}; UEFI mode: {SysInfo.has_uefi()}") debug(f'Hardware model detected: {SysInfo.sys_vendor()} {SysInfo.product_name()}; UEFI mode: {SysInfo.has_uefi()}')
debug(f"Processor model detected: {SysInfo.cpu_model()}") debug(f'Processor model detected: {SysInfo.cpu_model()}')
debug(f"Memory statistics: {SysInfo.mem_available()} available out of {SysInfo.mem_total()} total installed") debug(f'Memory statistics: {SysInfo.mem_available()} available out of {SysInfo.mem_total()} total installed')
debug(f"Virtualization detected: {SysInfo.virtualization()}; is VM: {SysInfo.is_vm()}") debug(f'Virtualization detected: {SysInfo.virtualization()}; is VM: {SysInfo.is_vm()}')
debug(f"Graphics devices detected: {SysInfo._graphics_devices().keys()}") debug(f'Graphics devices detected: {SysInfo._graphics_devices().keys()}')
# For support reasons, we'll log the disk layout pre installation to match against post-installation layout # For support reasons, we'll log the disk layout pre installation to match against post-installation layout
debug(f"Disk states before installing:\n{disk_layouts()}") debug(f'Disk states before installing:\n{disk_layouts()}')
def _fetch_arch_db() -> None: def _fetch_arch_db() -> None:
info("Fetching Arch Linux package database...") info('Fetching Arch Linux package database...')
try: try:
Pacman.run("-Sy") Pacman.run('-Sy')
except Exception as e: except Exception as e:
debug(f"Failed to sync Arch Linux package database: {e}") debug(f'Failed to sync Arch Linux package database: {e}')
exit(1) exit(1)
def _check_new_version() -> None: def _check_new_version() -> None:
info("Checking version...") info('Checking version...')
upgrade = None upgrade = None
try: try:
upgrade = Pacman.run("-Qu archinstall").decode() upgrade = Pacman.run('-Qu archinstall').decode()
except Exception as e: except Exception as e:
debug(f"Failed determine pacman version: {e}") debug(f'Failed determine pacman version: {e}')
if upgrade: if upgrade:
text = f"New version available: {upgrade}" text = f'New version available: {upgrade}'
info(text) info(text)
time.sleep(3) time.sleep(3)
@ -65,12 +65,12 @@ def main() -> int:
OR straight as a module: python -m archinstall OR straight as a module: python -m archinstall
In any case we will be attempting to load the provided script to be run from the scripts/ folder In any case we will be attempting to load the provided script to be run from the scripts/ folder
""" """
if "--help" in sys.argv or "-h" in sys.argv: if '--help' in sys.argv or '-h' in sys.argv:
arch_config_handler.print_help() arch_config_handler.print_help()
return 0 return 0
if os.getuid() != 0: if os.getuid() != 0:
print(tr("Archinstall requires root privileges to run. See --help for more.")) print(tr('Archinstall requires root privileges to run. See --help for more.'))
return 1 return 1
_log_sys_info() _log_sys_info()
@ -83,7 +83,7 @@ def main() -> int:
script = arch_config_handler.args.script script = arch_config_handler.args.script
mod_name = f"archinstall.scripts.{script}" mod_name = f'archinstall.scripts.{script}'
# by loading the module we'll automatically run the script # by loading the module we'll automatically run the script
importlib.import_module(mod_name) importlib.import_module(mod_name)
@ -103,11 +103,11 @@ def run_as_a_module() -> None:
Tui.shutdown() Tui.shutdown()
if exc: if exc:
err = "".join(traceback.format_exception(exc)) err = ''.join(traceback.format_exception(exc))
error(err) error(err)
text = ( text = (
"Archinstall experienced the above error. If you think this is a bug, please report it to\n" 'Archinstall experienced the above error. If you think this is a bug, please report it to\n'
'https://github.com/archlinux/archinstall and include the log file "/var/log/archinstall/install.log".\n\n' 'https://github.com/archlinux/archinstall and include the log file "/var/log/archinstall/install.log".\n\n'
"Hint: To extract the log from a live ISO \ncurl -F'file=@/var/log/archinstall/install.log' https://0x0.st\n" "Hint: To extract the log from a live ISO \ncurl -F'file=@/var/log/archinstall/install.log' https://0x0.st\n"
) )
@ -119,19 +119,19 @@ def run_as_a_module() -> None:
__all__ = [ __all__ = [
"FormattedOutput", 'FormattedOutput',
"Language", 'Language',
"Pacman", 'Pacman',
"SysInfo", 'SysInfo',
"Tui", 'Tui',
"arch_config_handler", 'arch_config_handler',
"debug", 'debug',
"disk_layouts", 'disk_layouts',
"error", 'error',
"info", 'info',
"load_plugin", 'load_plugin',
"log", 'log',
"plugin", 'plugin',
"translation_handler", 'translation_handler',
"warn", 'warn',
] ]

View File

@ -1,4 +1,4 @@
import archinstall import archinstall
if __name__ == "__main__": if __name__ == '__main__':
archinstall.run_as_a_module() archinstall.run_as_a_module()

View File

@ -9,22 +9,22 @@ if TYPE_CHECKING:
class PipewireProfile(Profile): class PipewireProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Pipewire", ProfileType.Application) super().__init__('Pipewire', ProfileType.Application)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"pipewire", 'pipewire',
"pipewire-alsa", 'pipewire-alsa',
"pipewire-jack", 'pipewire-jack',
"pipewire-pulse", 'pipewire-pulse',
"gst-plugin-pipewire", 'gst-plugin-pipewire',
"libpulse", 'libpulse',
"wireplumber", 'wireplumber',
] ]
def _enable_pipewire_for_all(self, install_session: "Installer") -> None: def _enable_pipewire_for_all(self, install_session: 'Installer') -> None:
from archinstall.lib.args import arch_config_handler from archinstall.lib.args import arch_config_handler
users: list[User] | None = arch_config_handler.config.users users: list[User] | None = arch_config_handler.config.users
@ -34,24 +34,24 @@ class PipewireProfile(Profile):
for user in users: for user in users:
# Create the full path for enabling the pipewire systemd items # Create the full path for enabling the pipewire systemd items
service_dir = install_session.target / "home" / user.username / ".config" / "systemd" / "user" / "default.target.wants" service_dir = install_session.target / 'home' / user.username / '.config' / 'systemd' / 'user' / 'default.target.wants'
service_dir.mkdir(parents=True, exist_ok=True) service_dir.mkdir(parents=True, exist_ok=True)
# Set ownership of the entire user catalogue # Set ownership of the entire user catalogue
install_session.arch_chroot(f"chown -R {user.username}:{user.username} /home/{user.username}") install_session.arch_chroot(f'chown -R {user.username}:{user.username} /home/{user.username}')
# symlink in the correct pipewire systemd items # symlink in the correct pipewire systemd items
install_session.arch_chroot( install_session.arch_chroot(
f"ln -sf /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service", f'ln -sf /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service',
run_as=user.username, run_as=user.username,
) )
install_session.arch_chroot( install_session.arch_chroot(
f"ln -sf /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket", f'ln -sf /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket',
run_as=user.username, run_as=user.username,
) )
@override @override
def install(self, install_session: "Installer") -> None: def install(self, install_session: 'Installer') -> None:
super().install(install_session) super().install(install_session)
install_session.add_additional_packages(self.packages) install_session.add_additional_packages(self.packages)
self._enable_pipewire_for_all(install_session) self._enable_pipewire_for_all(install_session)

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class DesktopProfile(Profile): class DesktopProfile(Profile):
def __init__(self, current_selection: list[Profile] = []) -> None: def __init__(self, current_selection: list[Profile] = []) -> None:
super().__init__( super().__init__(
"Desktop", 'Desktop',
ProfileType.Desktop, ProfileType.Desktop,
current_selection=current_selection, current_selection=current_selection,
support_greeter=True, support_greeter=True,
@ -25,16 +25,16 @@ class DesktopProfile(Profile):
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"nano", 'nano',
"vim", 'vim',
"openssh", 'openssh',
"htop", 'htop',
"wget", 'wget',
"iwd", 'iwd',
"wireless_tools", 'wireless_tools',
"wpa_supplicant", 'wpa_supplicant',
"smartmontools", 'smartmontools',
"xdg-utils", 'xdg-utils',
] ]
@property @property
@ -75,8 +75,8 @@ class DesktopProfile(Profile):
allow_reset=True, allow_reset=True,
allow_skip=True, allow_skip=True,
preview_style=PreviewStyle.RIGHT, preview_style=PreviewStyle.RIGHT,
preview_size="auto", preview_size='auto',
preview_frame=FrameProperties.max("Info"), preview_frame=FrameProperties.max('Info'),
).run() ).run()
match result.type_: match result.type_:
@ -90,17 +90,17 @@ class DesktopProfile(Profile):
return SelectResult.ResetCurrent return SelectResult.ResetCurrent
@override @override
def post_install(self, install_session: "Installer") -> None: def post_install(self, install_session: 'Installer') -> None:
for profile in self.current_selection: for profile in self.current_selection:
profile.post_install(install_session) profile.post_install(install_session)
@override @override
def install(self, install_session: "Installer") -> None: def install(self, install_session: 'Installer') -> None:
# Install common packages for all desktop environments # Install common packages for all desktop environments
install_session.add_additional_packages(self.packages) install_session.add_additional_packages(self.packages)
for profile in self.current_selection: for profile in self.current_selection:
info(f"Installing profile {profile.name}...") info(f'Installing profile {profile.name}...')
install_session.add_additional_packages(profile.packages) install_session.add_additional_packages(profile.packages)
install_session.enable_service(profile.services) install_session.enable_service(profile.services)

View File

@ -2,5 +2,5 @@ from enum import Enum
class SeatAccess(Enum): class SeatAccess(Enum):
seatd = "seatd" seatd = 'seatd'
polkit = "polkit" polkit = 'polkit'

View File

@ -9,56 +9,56 @@ if TYPE_CHECKING:
class AwesomeProfile(XorgProfile): class AwesomeProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Awesome", ProfileType.WindowMgr) super().__init__('Awesome', ProfileType.WindowMgr)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return super().packages + [ return super().packages + [
"awesome", 'awesome',
"alacritty", 'alacritty',
"xorg-xinit", 'xorg-xinit',
"xorg-xrandr", 'xorg-xrandr',
"xterm", 'xterm',
"feh", 'feh',
"slock", 'slock',
"terminus-font", 'terminus-font',
"gnu-free-fonts", 'gnu-free-fonts',
"ttf-liberation", 'ttf-liberation',
"xsel", 'xsel',
] ]
@override @override
def install(self, install_session: "Installer") -> None: def install(self, install_session: 'Installer') -> None:
super().install(install_session) super().install(install_session)
# TODO: Copy a full configuration to ~/.config/awesome/rc.lua instead. # TODO: Copy a full configuration to ~/.config/awesome/rc.lua instead.
with open(f"{install_session.target}/etc/xdg/awesome/rc.lua") as fh: with open(f'{install_session.target}/etc/xdg/awesome/rc.lua') as fh:
awesome_lua = fh.read() awesome_lua = fh.read()
# Replace xterm with alacritty for a smoother experience. # Replace xterm with alacritty for a smoother experience.
awesome_lua = awesome_lua.replace('"xterm"', '"alacritty"') awesome_lua = awesome_lua.replace('"xterm"', '"alacritty"')
with open(f"{install_session.target}/etc/xdg/awesome/rc.lua", "w") as fh: with open(f'{install_session.target}/etc/xdg/awesome/rc.lua', 'w') as fh:
fh.write(awesome_lua) fh.write(awesome_lua)
# TODO: Configure the right-click-menu to contain the above packages that were installed. (as a user config) # TODO: Configure the right-click-menu to contain the above packages that were installed. (as a user config)
# TODO: check if we selected a greeter, # TODO: check if we selected a greeter,
# but for now, awesome is intended to run without one. # but for now, awesome is intended to run without one.
with open(f"{install_session.target}/etc/X11/xinit/xinitrc") as xinitrc: with open(f'{install_session.target}/etc/X11/xinit/xinitrc') as xinitrc:
xinitrc_data = xinitrc.read() xinitrc_data = xinitrc.read()
for line in xinitrc_data.split("\n"): for line in xinitrc_data.split('\n'):
if "twm &" in line: if 'twm &' in line:
xinitrc_data = xinitrc_data.replace(line, f"# {line}") xinitrc_data = xinitrc_data.replace(line, f'# {line}')
if "xclock" in line: if 'xclock' in line:
xinitrc_data = xinitrc_data.replace(line, f"# {line}") xinitrc_data = xinitrc_data.replace(line, f'# {line}')
if "xterm" in line: if 'xterm' in line:
xinitrc_data = xinitrc_data.replace(line, f"# {line}") xinitrc_data = xinitrc_data.replace(line, f'# {line}')
xinitrc_data += "\n" xinitrc_data += '\n'
xinitrc_data += "exec awesome\n" xinitrc_data += 'exec awesome\n'
with open(f"{install_session.target}/etc/X11/xinit/xinitrc", "w") as xinitrc: with open(f'{install_session.target}/etc/X11/xinit/xinitrc', 'w') as xinitrc:
xinitrc.write(xinitrc_data) xinitrc.write(xinitrc_data)

View File

@ -6,18 +6,18 @@ from archinstall.default_profiles.xorg import XorgProfile
class BspwmProfile(XorgProfile): class BspwmProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Bspwm", ProfileType.WindowMgr) super().__init__('Bspwm', ProfileType.WindowMgr)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
# return super().packages + [ # return super().packages + [
return [ return [
"bspwm", 'bspwm',
"sxhkd", 'sxhkd',
"dmenu", 'dmenu',
"xdo", 'xdo',
"rxvt-unicode", 'rxvt-unicode',
] ]
@property @property

View File

@ -6,17 +6,17 @@ from archinstall.default_profiles.xorg import XorgProfile
class BudgieProfile(XorgProfile): class BudgieProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Budgie", ProfileType.DesktopEnv) super().__init__('Budgie', ProfileType.DesktopEnv)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"materia-gtk-theme", 'materia-gtk-theme',
"budgie", 'budgie',
"mate-terminal", 'mate-terminal',
"nemo", 'nemo',
"papirus-icon-theme", 'papirus-icon-theme',
] ]
@property @property

View File

@ -6,23 +6,23 @@ from archinstall.default_profiles.xorg import XorgProfile
class CinnamonProfile(XorgProfile): class CinnamonProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Cinnamon", ProfileType.DesktopEnv) super().__init__('Cinnamon', ProfileType.DesktopEnv)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"cinnamon", 'cinnamon',
"system-config-printer", 'system-config-printer',
"gnome-keyring", 'gnome-keyring',
"gnome-terminal", 'gnome-terminal',
"blueman", 'blueman',
"bluez-utils", 'bluez-utils',
"engrampa", 'engrampa',
"gnome-screenshot", 'gnome-screenshot',
"gvfs-smb", 'gvfs-smb',
"xed", 'xed',
"xdg-user-dirs-gtk", 'xdg-user-dirs-gtk',
] ]
@property @property

View File

@ -6,13 +6,13 @@ from archinstall.default_profiles.xorg import XorgProfile
class CosmicProfile(XorgProfile): class CosmicProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("cosmic-epoch", ProfileType.DesktopEnv, advanced=True) super().__init__('cosmic-epoch', ProfileType.DesktopEnv, advanced=True)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"cosmic", 'cosmic',
] ]
@property @property

View File

@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
class CutefishProfile(XorgProfile): class CutefishProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Cutefish", ProfileType.DesktopEnv) super().__init__('Cutefish', ProfileType.DesktopEnv)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"cutefish", 'cutefish',
"noto-fonts", 'noto-fonts',
] ]
@property @property

View File

@ -6,15 +6,15 @@ from archinstall.default_profiles.xorg import XorgProfile
class DeepinProfile(XorgProfile): class DeepinProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Deepin", ProfileType.DesktopEnv) super().__init__('Deepin', ProfileType.DesktopEnv)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"deepin", 'deepin',
"deepin-terminal", 'deepin-terminal',
"deepin-editor", 'deepin-editor',
] ]
@property @property

View File

@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
class EnlighenmentProfile(XorgProfile): class EnlighenmentProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Enlightenment", ProfileType.WindowMgr) super().__init__('Enlightenment', ProfileType.WindowMgr)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"enlightenment", 'enlightenment',
"terminology", 'terminology',
] ]
@property @property

View File

@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
class GnomeProfile(XorgProfile): class GnomeProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("GNOME", ProfileType.DesktopEnv) super().__init__('GNOME', ProfileType.DesktopEnv)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"gnome", 'gnome',
"gnome-tweaks", 'gnome-tweaks',
] ]
@property @property

View File

@ -12,26 +12,26 @@ from archinstall.tui.types import Alignment, FrameProperties
class HyprlandProfile(XorgProfile): class HyprlandProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Hyprland", ProfileType.DesktopEnv) super().__init__('Hyprland', ProfileType.DesktopEnv)
self.custom_settings = {"seat_access": None} self.custom_settings = {'seat_access': None}
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"hyprland", 'hyprland',
"dunst", 'dunst',
"kitty", 'kitty',
"uwsm", 'uwsm',
"dolphin", 'dolphin',
"wofi", 'wofi',
"xdg-desktop-portal-hyprland", 'xdg-desktop-portal-hyprland',
"qt5-wayland", 'qt5-wayland',
"qt6-wayland", 'qt6-wayland',
"polkit-kde-agent", 'polkit-kde-agent',
"grim", 'grim',
"slurp", 'slurp',
] ]
@property @property
@ -42,32 +42,32 @@ class HyprlandProfile(XorgProfile):
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
if pref := self.custom_settings.get("seat_access", None): if pref := self.custom_settings.get('seat_access', None):
return [pref] return [pref]
return [] return []
def _ask_seat_access(self) -> None: def _ask_seat_access(self) -> None:
# need to activate seat service and add to seat group # need to activate seat service and add to seat group
header = tr("Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)") header = tr('Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)')
header += "\n" + tr("Choose an option to give Hyprland access to your hardware") + "\n" header += '\n' + tr('Choose an option to give Hyprland access to your hardware') + '\n'
items = [MenuItem(s.value, value=s) for s in SeatAccess] items = [MenuItem(s.value, value=s) for s in SeatAccess]
group = MenuItemGroup(items, sort_items=True) group = MenuItemGroup(items, sort_items=True)
default = self.custom_settings.get("seat_access", None) default = self.custom_settings.get('seat_access', None)
group.set_default_by_value(default) group.set_default_by_value(default)
result = SelectMenu[SeatAccess]( result = SelectMenu[SeatAccess](
group, group,
header=header, header=header,
allow_skip=False, allow_skip=False,
frame=FrameProperties.min(tr("Seat access")), frame=FrameProperties.min(tr('Seat access')),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
).run() ).run()
if result.type_ == ResultType.Selection: if result.type_ == ResultType.Selection:
if result.item() is not None: if result.item() is not None:
self.custom_settings["seat_access"] = result.get_value().value self.custom_settings['seat_access'] = result.get_value().value
@override @override
def do_on_select(self) -> None: def do_on_select(self) -> None:

View File

@ -6,21 +6,21 @@ from archinstall.default_profiles.xorg import XorgProfile
class I3wmProfile(XorgProfile): class I3wmProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("i3-wm", ProfileType.WindowMgr) super().__init__('i3-wm', ProfileType.WindowMgr)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"i3-wm", 'i3-wm',
"i3lock", 'i3lock',
"i3status", 'i3status',
"i3blocks", 'i3blocks',
"xss-lock", 'xss-lock',
"xterm", 'xterm',
"lightdm-gtk-greeter", 'lightdm-gtk-greeter',
"lightdm", 'lightdm',
"dmenu", 'dmenu',
] ]
@property @property

View File

@ -13,22 +13,22 @@ from archinstall.tui.types import Alignment, FrameProperties
class LabwcProfile(XorgProfile): class LabwcProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Labwc", 'Labwc',
ProfileType.WindowMgr, ProfileType.WindowMgr,
) )
self.custom_settings = {"seat_access": None} self.custom_settings = {'seat_access': None}
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
additional = [] additional = []
if seat := self.custom_settings.get("seat_access", None): if seat := self.custom_settings.get('seat_access', None):
additional = [seat] additional = [seat]
return [ return [
"alacritty", 'alacritty',
"labwc", 'labwc',
] + additional ] + additional
@property @property
@ -39,32 +39,32 @@ class LabwcProfile(XorgProfile):
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
if pref := self.custom_settings.get("seat_access", None): if pref := self.custom_settings.get('seat_access', None):
return [pref] return [pref]
return [] return []
def _ask_seat_access(self) -> None: def _ask_seat_access(self) -> None:
# need to activate seat service and add to seat group # need to activate seat service and add to seat group
header = tr("labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)") header = tr('labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)')
header += "\n" + tr("Choose an option to give labwc access to your hardware") + "\n" header += '\n' + tr('Choose an option to give labwc access to your hardware') + '\n'
items = [MenuItem(s.value, value=s) for s in SeatAccess] items = [MenuItem(s.value, value=s) for s in SeatAccess]
group = MenuItemGroup(items, sort_items=True) group = MenuItemGroup(items, sort_items=True)
default = self.custom_settings.get("seat_access", None) default = self.custom_settings.get('seat_access', None)
group.set_default_by_value(default) group.set_default_by_value(default)
result = SelectMenu[SeatAccess]( result = SelectMenu[SeatAccess](
group, group,
header=header, header=header,
allow_skip=False, allow_skip=False,
frame=FrameProperties.min(tr("Seat access")), frame=FrameProperties.min(tr('Seat access')),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
).run() ).run()
if result.type_ == ResultType.Selection: if result.type_ == ResultType.Selection:
if result.item() is not None: if result.item() is not None:
self.custom_settings["seat_access"] = result.get_value().value self.custom_settings['seat_access'] = result.get_value().value
@override @override
def do_on_select(self) -> None: def do_on_select(self) -> None:

View File

@ -6,7 +6,7 @@ from archinstall.default_profiles.xorg import XorgProfile
class LxqtProfile(XorgProfile): class LxqtProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Lxqt", ProfileType.DesktopEnv) super().__init__('Lxqt', ProfileType.DesktopEnv)
# NOTE: SDDM is the only officially supported greeter for LXQt, so unlike other DEs, lightdm is not used here. # NOTE: SDDM is the only officially supported greeter for LXQt, so unlike other DEs, lightdm is not used here.
# LXQt works with lightdm, but since this is not supported, we will not default to this. # LXQt works with lightdm, but since this is not supported, we will not default to this.
@ -15,13 +15,13 @@ class LxqtProfile(XorgProfile):
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"lxqt", 'lxqt',
"breeze-icons", 'breeze-icons',
"oxygen-icons", 'oxygen-icons',
"xdg-utils", 'xdg-utils',
"ttf-freefont", 'ttf-freefont',
"leafpad", 'leafpad',
"slock", 'slock',
] ]
@property @property

View File

@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
class MateProfile(XorgProfile): class MateProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Mate", ProfileType.DesktopEnv) super().__init__('Mate', ProfileType.DesktopEnv)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"mate", 'mate',
"mate-extra", 'mate-extra',
] ]
@property @property

View File

@ -13,30 +13,30 @@ from archinstall.tui.types import Alignment, FrameProperties
class NiriProfile(XorgProfile): class NiriProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Niri", 'Niri',
ProfileType.WindowMgr, ProfileType.WindowMgr,
) )
self.custom_settings = {"seat_access": None} self.custom_settings = {'seat_access': None}
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
additional = [] additional = []
if seat := self.custom_settings.get("seat_access", None): if seat := self.custom_settings.get('seat_access', None):
additional = [seat] additional = [seat]
return [ return [
"niri", 'niri',
"alacritty", 'alacritty',
"fuzzel", 'fuzzel',
"mako", 'mako',
"xorg-xwayland", 'xorg-xwayland',
"waybar", 'waybar',
"swaybg", 'swaybg',
"swayidle", 'swayidle',
"swaylock", 'swaylock',
"xdg-desktop-portal-gnome", 'xdg-desktop-portal-gnome',
] + additional ] + additional
@property @property
@ -47,32 +47,32 @@ class NiriProfile(XorgProfile):
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
if pref := self.custom_settings.get("seat_access", None): if pref := self.custom_settings.get('seat_access', None):
return [pref] return [pref]
return [] return []
def _ask_seat_access(self) -> None: def _ask_seat_access(self) -> None:
# need to activate seat service and add to seat group # need to activate seat service and add to seat group
header = tr("niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)") header = tr('niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)')
header += "\n" + tr("Choose an option to give niri access to your hardware") + "\n" header += '\n' + tr('Choose an option to give niri access to your hardware') + '\n'
items = [MenuItem(s.value, value=s) for s in SeatAccess] items = [MenuItem(s.value, value=s) for s in SeatAccess]
group = MenuItemGroup(items, sort_items=True) group = MenuItemGroup(items, sort_items=True)
default = self.custom_settings.get("seat_access", None) default = self.custom_settings.get('seat_access', None)
group.set_default_by_value(default) group.set_default_by_value(default)
result = SelectMenu[SeatAccess]( result = SelectMenu[SeatAccess](
group, group,
header=header, header=header,
allow_skip=False, allow_skip=False,
frame=FrameProperties.min(tr("Seat access")), frame=FrameProperties.min(tr('Seat access')),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
).run() ).run()
if result.type_ == ResultType.Selection: if result.type_ == ResultType.Selection:
if result.item() is not None: if result.item() is not None:
self.custom_settings["seat_access"] = result.get_value().value self.custom_settings['seat_access'] = result.get_value().value
@override @override
def do_on_select(self) -> None: def do_on_select(self) -> None:

View File

@ -6,18 +6,18 @@ from archinstall.default_profiles.xorg import XorgProfile
class PlasmaProfile(XorgProfile): class PlasmaProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("KDE Plasma", ProfileType.DesktopEnv) super().__init__('KDE Plasma', ProfileType.DesktopEnv)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"plasma-meta", 'plasma-meta',
"konsole", 'konsole',
"kate", 'kate',
"dolphin", 'dolphin',
"ark", 'ark',
"plasma-workspace", 'plasma-workspace',
] ]
@property @property

View File

@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
class QtileProfile(XorgProfile): class QtileProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Qtile", ProfileType.WindowMgr) super().__init__('Qtile', ProfileType.WindowMgr)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"qtile", 'qtile',
"alacritty", 'alacritty',
] ]
@property @property

View File

@ -6,15 +6,15 @@ from archinstall.default_profiles.xorg import XorgProfile
class RiverProfile(XorgProfile): class RiverProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("River", ProfileType.WindowMgr) super().__init__('River', ProfileType.WindowMgr)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"foot", 'foot',
"xdg-desktop-portal-wlr", 'xdg-desktop-portal-wlr',
"river", 'river',
] ]
@property @property

View File

@ -13,32 +13,32 @@ from archinstall.tui.types import Alignment, FrameProperties
class SwayProfile(XorgProfile): class SwayProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Sway", 'Sway',
ProfileType.WindowMgr, ProfileType.WindowMgr,
) )
self.custom_settings = {"seat_access": None} self.custom_settings = {'seat_access': None}
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
additional = [] additional = []
if seat := self.custom_settings.get("seat_access", None): if seat := self.custom_settings.get('seat_access', None):
additional = [seat] additional = [seat]
return [ return [
"sway", 'sway',
"swaybg", 'swaybg',
"swaylock", 'swaylock',
"swayidle", 'swayidle',
"waybar", 'waybar',
"wmenu", 'wmenu',
"brightnessctl", 'brightnessctl',
"grim", 'grim',
"slurp", 'slurp',
"pavucontrol", 'pavucontrol',
"foot", 'foot',
"xorg-xwayland", 'xorg-xwayland',
] + additional ] + additional
@property @property
@ -49,32 +49,32 @@ class SwayProfile(XorgProfile):
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
if pref := self.custom_settings.get("seat_access", None): if pref := self.custom_settings.get('seat_access', None):
return [pref] return [pref]
return [] return []
def _ask_seat_access(self) -> None: def _ask_seat_access(self) -> None:
# need to activate seat service and add to seat group # need to activate seat service and add to seat group
header = tr("Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)") header = tr('Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)')
header += "\n" + tr("Choose an option to give Sway access to your hardware") + "\n" header += '\n' + tr('Choose an option to give Sway access to your hardware') + '\n'
items = [MenuItem(s.value, value=s) for s in SeatAccess] items = [MenuItem(s.value, value=s) for s in SeatAccess]
group = MenuItemGroup(items, sort_items=True) group = MenuItemGroup(items, sort_items=True)
default = self.custom_settings.get("seat_access", None) default = self.custom_settings.get('seat_access', None)
group.set_default_by_value(default) group.set_default_by_value(default)
result = SelectMenu[SeatAccess]( result = SelectMenu[SeatAccess](
group, group,
header=header, header=header,
allow_skip=False, allow_skip=False,
frame=FrameProperties.min(tr("Seat access")), frame=FrameProperties.min(tr('Seat access')),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
).run() ).run()
if result.type_ == ResultType.Selection: if result.type_ == ResultType.Selection:
if result.item() is not None: if result.item() is not None:
self.custom_settings["seat_access"] = result.get_value().value self.custom_settings['seat_access'] = result.get_value().value
@override @override
def do_on_select(self) -> None: def do_on_select(self) -> None:

View File

@ -6,17 +6,17 @@ from archinstall.default_profiles.xorg import XorgProfile
class Xfce4Profile(XorgProfile): class Xfce4Profile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Xfce4", ProfileType.DesktopEnv) super().__init__('Xfce4', ProfileType.DesktopEnv)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"xfce4", 'xfce4',
"xfce4-goodies", 'xfce4-goodies',
"pavucontrol", 'pavucontrol',
"gvfs", 'gvfs',
"xarchiver", 'xarchiver',
] ]
@property @property

View File

@ -6,17 +6,17 @@ from archinstall.default_profiles.xorg import XorgProfile
class XmonadProfile(XorgProfile): class XmonadProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Xmonad", ProfileType.WindowMgr) super().__init__('Xmonad', ProfileType.WindowMgr)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"xmonad", 'xmonad',
"xmonad-contrib", 'xmonad-contrib',
"xmonad-extra", 'xmonad-extra',
"xterm", 'xterm',
"dmenu", 'dmenu',
] ]
@property @property

View File

@ -4,6 +4,6 @@ from archinstall.default_profiles.profile import Profile, ProfileType
class MinimalProfile(Profile): class MinimalProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Minimal", 'Minimal',
ProfileType.Minimal, ProfileType.Minimal,
) )

View File

@ -12,31 +12,31 @@ if TYPE_CHECKING:
class ProfileType(Enum): class ProfileType(Enum):
# top level default_profiles # top level default_profiles
Server = "Server" Server = 'Server'
Desktop = "Desktop" Desktop = 'Desktop'
Xorg = "Xorg" Xorg = 'Xorg'
Minimal = "Minimal" Minimal = 'Minimal'
Custom = "Custom" Custom = 'Custom'
# detailed selection default_profiles # detailed selection default_profiles
ServerType = "ServerType" ServerType = 'ServerType'
WindowMgr = "Window Manager" WindowMgr = 'Window Manager'
DesktopEnv = "Desktop Environment" DesktopEnv = 'Desktop Environment'
CustomType = "CustomType" CustomType = 'CustomType'
# special things # special things
Tailored = "Tailored" Tailored = 'Tailored'
Application = "Application" Application = 'Application'
class GreeterType(Enum): class GreeterType(Enum):
Lightdm = "lightdm-gtk-greeter" Lightdm = 'lightdm-gtk-greeter'
LightdmSlick = "lightdm-slick-greeter" LightdmSlick = 'lightdm-slick-greeter'
Sddm = "sddm" Sddm = 'sddm'
Gdm = "gdm" Gdm = 'gdm'
Ly = "ly" Ly = 'ly'
# .. todo:: Remove when we un-hide cosmic behind --advanced # .. todo:: Remove when we un-hide cosmic behind --advanced
if "--advanced" in sys.argv: if '--advanced' in sys.argv:
CosmicSession = "cosmic-greeter" CosmicSession = 'cosmic-greeter'
class SelectResult(Enum): class SelectResult(Enum):
@ -106,12 +106,12 @@ class Profile:
return self.advanced is False or arch_config_handler.args.advanced is True return self.advanced is False or arch_config_handler.args.advanced is True
def install(self, install_session: "Installer") -> None: def install(self, install_session: 'Installer') -> None:
""" """
Performs installation steps when this profile was selected Performs installation steps when this profile was selected
""" """
def post_install(self, install_session: "Installer") -> None: def post_install(self, install_session: 'Installer') -> None:
""" """
Hook that will be called when the installation process is Hook that will be called when the installation process is
finished and custom installation steps for specific default_profiles finished and custom installation steps for specific default_profiles
@ -196,9 +196,9 @@ class Profile:
if sub_profile.packages: if sub_profile.packages:
packages.update(sub_profile.packages) packages.update(sub_profile.packages)
text = tr("Installed packages") + ":\n" text = tr('Installed packages') + ':\n'
for pkg in sorted(packages): for pkg in sorted(packages):
text += f"\t- {pkg}\n" text += f'\t- {pkg}\n'
return text return text

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class ServerProfile(Profile): class ServerProfile(Profile):
def __init__(self, current_value: list[Profile] = []): def __init__(self, current_value: list[Profile] = []):
super().__init__( super().__init__(
"Server", 'Server',
ProfileType.Server, ProfileType.Server,
current_selection=current_value, current_selection=current_value,
) )
@ -39,8 +39,8 @@ class ServerProfile(Profile):
allow_reset=True, allow_reset=True,
allow_skip=True, allow_skip=True,
preview_style=PreviewStyle.RIGHT, preview_style=PreviewStyle.RIGHT,
preview_size="auto", preview_size='auto',
preview_frame=FrameProperties.max("Info"), preview_frame=FrameProperties.max('Info'),
multi=True, multi=True,
).run() ).run()
@ -55,20 +55,20 @@ class ServerProfile(Profile):
return SelectResult.ResetCurrent return SelectResult.ResetCurrent
@override @override
def post_install(self, install_session: "Installer") -> None: def post_install(self, install_session: 'Installer') -> None:
for profile in self.current_selection: for profile in self.current_selection:
profile.post_install(install_session) profile.post_install(install_session)
@override @override
def install(self, install_session: "Installer") -> None: def install(self, install_session: 'Installer') -> None:
server_info = self.current_selection_names() server_info = self.current_selection_names()
details = ", ".join(server_info) details = ', '.join(server_info)
info(f"Now installing the selected servers: {details}") info(f'Now installing the selected servers: {details}')
for server in self.current_selection: for server in self.current_selection:
info(f"Installing {server.name}...") info(f'Installing {server.name}...')
install_session.add_additional_packages(server.packages) install_session.add_additional_packages(server.packages)
install_session.enable_service(server.services) install_session.enable_service(server.services)
server.install(install_session) server.install(install_session)
info("If your selections included multiple servers with the same port, you may have to reconfigure them.") info('If your selections included multiple servers with the same port, you may have to reconfigure them.')

View File

@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
class CockpitProfile(Profile): class CockpitProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Cockpit", 'Cockpit',
ProfileType.ServerType, ProfileType.ServerType,
) )
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return ["cockpit", "udisks2", "packagekit"] return ['cockpit', 'udisks2', 'packagekit']
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
return ["cockpit.socket"] return ['cockpit.socket']

View File

@ -9,23 +9,23 @@ if TYPE_CHECKING:
class DockerProfile(Profile): class DockerProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Docker", 'Docker',
ProfileType.ServerType, ProfileType.ServerType,
) )
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return ["docker"] return ['docker']
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
return ["docker"] return ['docker']
@override @override
def post_install(self, install_session: "Installer") -> None: def post_install(self, install_session: 'Installer') -> None:
from archinstall.lib.args import arch_config_handler from archinstall.lib.args import arch_config_handler
for user in arch_config_handler.config.users: for user in arch_config_handler.config.users:
install_session.arch_chroot(f"usermod -a -G docker {user.username}") install_session.arch_chroot(f'usermod -a -G docker {user.username}')

View File

@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
class HttpdProfile(Profile): class HttpdProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"httpd", 'httpd',
ProfileType.ServerType, ProfileType.ServerType,
) )
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return ["apache"] return ['apache']
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
return ["httpd"] return ['httpd']

View File

@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
class LighttpdProfile(Profile): class LighttpdProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Lighttpd", 'Lighttpd',
ProfileType.ServerType, ProfileType.ServerType,
) )
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return ["lighttpd"] return ['lighttpd']
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
return ["lighttpd"] return ['lighttpd']

View File

@ -9,20 +9,20 @@ if TYPE_CHECKING:
class MariadbProfile(Profile): class MariadbProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Mariadb", 'Mariadb',
ProfileType.ServerType, ProfileType.ServerType,
) )
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return ["mariadb"] return ['mariadb']
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
return ["mariadb"] return ['mariadb']
@override @override
def post_install(self, install_session: "Installer") -> None: def post_install(self, install_session: 'Installer') -> None:
install_session.arch_chroot("mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql") install_session.arch_chroot('mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql')

View File

@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
class NginxProfile(Profile): class NginxProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Nginx", 'Nginx',
ProfileType.ServerType, ProfileType.ServerType,
) )
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return ["nginx"] return ['nginx']
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
return ["nginx"] return ['nginx']

View File

@ -9,20 +9,20 @@ if TYPE_CHECKING:
class PostgresqlProfile(Profile): class PostgresqlProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Postgresql", 'Postgresql',
ProfileType.ServerType, ProfileType.ServerType,
) )
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return ["postgresql"] return ['postgresql']
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
return ["postgresql"] return ['postgresql']
@override @override
def post_install(self, install_session: "Installer") -> None: def post_install(self, install_session: 'Installer') -> None:
install_session.arch_chroot("initdb -D /var/lib/postgres/data", run_as="postgres") install_session.arch_chroot('initdb -D /var/lib/postgres/data', run_as='postgres')

View File

@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
class SshdProfile(Profile): class SshdProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"sshd", 'sshd',
ProfileType.ServerType, ProfileType.ServerType,
) )
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return ["openssh"] return ['openssh']
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
return ["sshd"] return ['sshd']

View File

@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
class TomcatProfile(Profile): class TomcatProfile(Profile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__( super().__init__(
"Tomcat", 'Tomcat',
ProfileType.ServerType, ProfileType.ServerType,
) )
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return ["tomcat10"] return ['tomcat10']
@property @property
@override @override
def services(self) -> list[str]: def services(self) -> list[str]:
return ["tomcat10"] return ['tomcat10']

View File

@ -9,14 +9,14 @@ if TYPE_CHECKING:
class TailoredProfile(XorgProfile): class TailoredProfile(XorgProfile):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("52-54-00-12-34-56", ProfileType.Tailored) super().__init__('52-54-00-12-34-56', ProfileType.Tailored)
@property @property
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return ["nano", "wget", "git"] return ['nano', 'wget', 'git']
@override @override
def install(self, install_session: "Installer") -> None: def install(self, install_session: 'Installer') -> None:
super().install(install_session) super().install(install_session)
# do whatever you like here :) # do whatever you like here :)

View File

@ -7,7 +7,7 @@ from archinstall.lib.translationhandler import tr
class XorgProfile(Profile): class XorgProfile(Profile):
def __init__( def __init__(
self, self,
name: str = "Xorg", name: str = 'Xorg',
profile_type: ProfileType = ProfileType.Xorg, profile_type: ProfileType = ProfileType.Xorg,
advanced: bool = False, advanced: bool = False,
): ):
@ -20,9 +20,9 @@ class XorgProfile(Profile):
@override @override
def preview_text(self) -> str: def preview_text(self) -> str:
text = tr("Environment type: {}").format(self.profile_type.value) text = tr('Environment type: {}').format(self.profile_type.value)
if packages := self.packages_text(): if packages := self.packages_text():
text += f"\n{packages}" text += f'\n{packages}'
return text return text
@ -30,5 +30,5 @@ class XorgProfile(Profile):
@override @override
def packages(self) -> list[str]: def packages(self) -> list[str]:
return [ return [
"xorg-server", 'xorg-server',
] ]

View File

@ -39,8 +39,8 @@ class Arguments:
creds_decryption_key: str | None = None creds_decryption_key: str | None = None
silent: bool = False silent: bool = False
dry_run: bool = False dry_run: bool = False
script: str = "guided" script: str = 'guided'
mountpoint: Path = Path("/mnt") mountpoint: Path = Path('/mnt')
skip_ntp: bool = False skip_ntp: bool = False
skip_wkd: bool = False skip_wkd: bool = False
debug: bool = False debug: bool = False
@ -56,7 +56,7 @@ class Arguments:
class ArchConfig: class ArchConfig:
version: str | None = None version: str | None = None
locale_config: LocaleConfiguration | None = None locale_config: LocaleConfiguration | None = None
archinstall_language: Language = field(default_factory=lambda: translation_handler.get_language_by_abbr("en")) archinstall_language: Language = field(default_factory=lambda: translation_handler.get_language_by_abbr('en'))
disk_config: DiskLayoutConfiguration | None = None disk_config: DiskLayoutConfiguration | None = None
profile_config: ProfileConfiguration | None = None profile_config: ProfileConfiguration | None = None
mirror_config: MirrorConfiguration | None = None mirror_config: MirrorConfiguration | None = None
@ -64,13 +64,13 @@ class ArchConfig:
bootloader: Bootloader = field(default=Bootloader.get_default()) bootloader: Bootloader = field(default=Bootloader.get_default())
uki: bool = False uki: bool = False
audio_config: AudioConfiguration | None = None audio_config: AudioConfiguration | None = None
hostname: str = "archlinux" hostname: str = 'archlinux'
kernels: list[str] = field(default_factory=lambda: ["linux"]) kernels: list[str] = field(default_factory=lambda: ['linux'])
ntp: bool = True ntp: bool = True
packages: list[str] = field(default_factory=list) packages: list[str] = field(default_factory=list)
parallel_downloads: int = 0 parallel_downloads: int = 0
swap: bool = True swap: bool = True
timezone: str = "UTC" timezone: str = 'UTC'
services: list[str] = field(default_factory=list) services: list[str] = field(default_factory=list)
custom_commands: list[str] = field(default_factory=list) custom_commands: list[str] = field(default_factory=list)
@ -81,70 +81,70 @@ class ArchConfig:
def unsafe_json(self) -> dict[str, Any]: def unsafe_json(self) -> dict[str, Any]:
config = { config = {
"users": [user.json() for user in self.users], 'users': [user.json() for user in self.users],
"root_enc_password": self.root_enc_password.enc_password if self.root_enc_password else None, 'root_enc_password': self.root_enc_password.enc_password if self.root_enc_password else None,
} }
if self.disk_encryption and self.disk_encryption.encryption_password: if self.disk_encryption and self.disk_encryption.encryption_password:
config["encryption_password"] = self.disk_encryption.encryption_password.plaintext config['encryption_password'] = self.disk_encryption.encryption_password.plaintext
return config return config
def safe_json(self) -> dict[str, Any]: def safe_json(self) -> dict[str, Any]:
config: Any = { config: Any = {
"version": self.version, 'version': self.version,
"archinstall-language": self.archinstall_language.json(), 'archinstall-language': self.archinstall_language.json(),
"hostname": self.hostname, 'hostname': self.hostname,
"kernels": self.kernels, 'kernels': self.kernels,
"ntp": self.ntp, 'ntp': self.ntp,
"packages": self.packages, 'packages': self.packages,
"parallel_downloads": self.parallel_downloads, 'parallel_downloads': self.parallel_downloads,
"swap": self.swap, 'swap': self.swap,
"timezone": self.timezone, 'timezone': self.timezone,
"services": self.services, 'services': self.services,
"custom_commands": self.custom_commands, 'custom_commands': self.custom_commands,
"bootloader": self.bootloader.json(), 'bootloader': self.bootloader.json(),
"audio_config": self.audio_config.json() if self.audio_config else None, 'audio_config': self.audio_config.json() if self.audio_config else None,
} }
if self.locale_config: if self.locale_config:
config["locale_config"] = self.locale_config.json() config['locale_config'] = self.locale_config.json()
if self.disk_config: if self.disk_config:
config["disk_config"] = self.disk_config.json() config['disk_config'] = self.disk_config.json()
if self.disk_encryption: if self.disk_encryption:
config["disk_encryption"] = self.disk_encryption.json() config['disk_encryption'] = self.disk_encryption.json()
if self.profile_config: if self.profile_config:
config["profile_config"] = self.profile_config.json() config['profile_config'] = self.profile_config.json()
if self.mirror_config: if self.mirror_config:
config["mirror_config"] = self.mirror_config.json() config['mirror_config'] = self.mirror_config.json()
if self.network_config: if self.network_config:
config["network_config"] = self.network_config.json() config['network_config'] = self.network_config.json()
return config return config
@classmethod @classmethod
def from_config(cls, args_config: dict[str, Any]) -> "ArchConfig": def from_config(cls, args_config: dict[str, Any]) -> 'ArchConfig':
arch_config = ArchConfig() arch_config = ArchConfig()
arch_config.locale_config = LocaleConfiguration.parse_arg(args_config) arch_config.locale_config = LocaleConfiguration.parse_arg(args_config)
if archinstall_lang := args_config.get("archinstall-language", None): if archinstall_lang := args_config.get('archinstall-language', None):
arch_config.archinstall_language = translation_handler.get_language_by_name(archinstall_lang) arch_config.archinstall_language = translation_handler.get_language_by_name(archinstall_lang)
if disk_config := args_config.get("disk_config", {}): if disk_config := args_config.get('disk_config', {}):
arch_config.disk_config = DiskLayoutConfiguration.parse_arg(disk_config) arch_config.disk_config = DiskLayoutConfiguration.parse_arg(disk_config)
if profile_config := args_config.get("profile_config", None): if profile_config := args_config.get('profile_config', None):
arch_config.profile_config = ProfileConfiguration.parse_arg(profile_config) arch_config.profile_config = ProfileConfiguration.parse_arg(profile_config)
if mirror_config := args_config.get("mirror_config", None): if mirror_config := args_config.get('mirror_config', None):
backwards_compatible_repo = [] backwards_compatible_repo = []
if additional_repositories := args_config.get("additional-repositories", []): if additional_repositories := args_config.get('additional-repositories', []):
backwards_compatible_repo = [Repository(r) for r in additional_repositories] backwards_compatible_repo = [Repository(r) for r in additional_repositories]
arch_config.mirror_config = MirrorConfiguration.parse_args( arch_config.mirror_config = MirrorConfiguration.parse_args(
@ -152,62 +152,62 @@ class ArchConfig:
backwards_compatible_repo, backwards_compatible_repo,
) )
if net_config := args_config.get("network_config", None): if net_config := args_config.get('network_config', None):
arch_config.network_config = NetworkConfiguration.parse_arg(net_config) arch_config.network_config = NetworkConfiguration.parse_arg(net_config)
# DEPRECATED: backwards copatibility # DEPRECATED: backwards copatibility
if users := args_config.get("!users", None): if users := args_config.get('!users', None):
arch_config.users = User.parse_arguments(users) arch_config.users = User.parse_arguments(users)
if users := args_config.get("users", None): if users := args_config.get('users', None):
arch_config.users = User.parse_arguments(users) arch_config.users = User.parse_arguments(users)
if bootloader_config := args_config.get("bootloader", None): if bootloader_config := args_config.get('bootloader', None):
arch_config.bootloader = Bootloader.from_arg(bootloader_config) arch_config.bootloader = Bootloader.from_arg(bootloader_config)
if args_config.get("uki") and not arch_config.bootloader.has_uki_support(): if args_config.get('uki') and not arch_config.bootloader.has_uki_support():
arch_config.uki = False arch_config.uki = False
if audio_config := args_config.get("audio_config", None): if audio_config := args_config.get('audio_config', None):
arch_config.audio_config = AudioConfiguration.parse_arg(audio_config) arch_config.audio_config = AudioConfiguration.parse_arg(audio_config)
if args_config.get("disk_encryption", None) is not None and arch_config.disk_config is not None: if args_config.get('disk_encryption', None) is not None and arch_config.disk_config is not None:
arch_config.disk_encryption = DiskEncryption.parse_arg( arch_config.disk_encryption = DiskEncryption.parse_arg(
arch_config.disk_config, arch_config.disk_config,
args_config["disk_encryption"], args_config['disk_encryption'],
Password(plaintext=args_config.get("encryption_password", "")), Password(plaintext=args_config.get('encryption_password', '')),
) )
if hostname := args_config.get("hostname", ""): if hostname := args_config.get('hostname', ''):
arch_config.hostname = hostname arch_config.hostname = hostname
if kernels := args_config.get("kernels", []): if kernels := args_config.get('kernels', []):
arch_config.kernels = kernels arch_config.kernels = kernels
arch_config.ntp = args_config.get("ntp", True) arch_config.ntp = args_config.get('ntp', True)
if packages := args_config.get("packages", []): if packages := args_config.get('packages', []):
arch_config.packages = packages arch_config.packages = packages
if parallel_downloads := args_config.get("parallel_downloads", 0): if parallel_downloads := args_config.get('parallel_downloads', 0):
arch_config.parallel_downloads = parallel_downloads arch_config.parallel_downloads = parallel_downloads
arch_config.swap = args_config.get("swap", True) arch_config.swap = args_config.get('swap', True)
if timezone := args_config.get("timezone", "UTC"): if timezone := args_config.get('timezone', 'UTC'):
arch_config.timezone = timezone arch_config.timezone = timezone
if services := args_config.get("services", []): if services := args_config.get('services', []):
arch_config.services = services arch_config.services = services
# DEPRECATED: backwards compatibility # DEPRECATED: backwards compatibility
if root_password := args_config.get("!root-password", None): if root_password := args_config.get('!root-password', None):
arch_config.root_enc_password = Password(plaintext=root_password) arch_config.root_enc_password = Password(plaintext=root_password)
if enc_password := args_config.get("root_enc_password", None): if enc_password := args_config.get('root_enc_password', None):
arch_config.root_enc_password = Password(enc_password=enc_password) arch_config.root_enc_password = Password(enc_password=enc_password)
if custom_commands := args_config.get("custom_commands", []): if custom_commands := args_config.get('custom_commands', []):
arch_config.custom_commands = custom_commands arch_config.custom_commands = custom_commands
return arch_config return arch_config
@ -239,135 +239,135 @@ class ArchConfigHandler:
def _get_version(self) -> str: def _get_version(self) -> str:
try: try:
return version("archinstall") return version('archinstall')
except Exception: except Exception:
return "Archinstall version not found" return 'Archinstall version not found'
def _define_arguments(self) -> ArgumentParser: def _define_arguments(self) -> ArgumentParser:
parser = ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser = ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument( parser.add_argument(
"-v", '-v',
"--version", '--version',
action="version", action='version',
default=False, default=False,
version="%(prog)s " + self._get_version(), version='%(prog)s ' + self._get_version(),
) )
parser.add_argument( parser.add_argument(
"--config", '--config',
type=Path, type=Path,
nargs="?", nargs='?',
default=None, default=None,
help="JSON configuration file", help='JSON configuration file',
) )
parser.add_argument( parser.add_argument(
"--config-url", '--config-url',
type=str, type=str,
nargs="?", nargs='?',
default=None, default=None,
help="Url to a JSON configuration file", help='Url to a JSON configuration file',
) )
parser.add_argument( parser.add_argument(
"--creds", '--creds',
type=Path, type=Path,
nargs="?", nargs='?',
default=None, default=None,
help="JSON credentials configuration file", help='JSON credentials configuration file',
) )
parser.add_argument( parser.add_argument(
"--creds-url", '--creds-url',
type=str, type=str,
nargs="?", nargs='?',
default=None, default=None,
help="Url to a JSON credentials configuration file", help='Url to a JSON credentials configuration file',
) )
parser.add_argument( parser.add_argument(
"--creds-decryption-key", '--creds-decryption-key',
type=str, type=str,
nargs="?", nargs='?',
default=None, default=None,
help="Decryption key for credentials file", help='Decryption key for credentials file',
) )
parser.add_argument( parser.add_argument(
"--silent", '--silent',
action="store_true", action='store_true',
default=False, default=False,
help="WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored", help='WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored',
) )
parser.add_argument( parser.add_argument(
"--dry-run", '--dry-run',
"--dry_run", '--dry_run',
action="store_true", action='store_true',
default=False, default=False,
help="Generates a configuration file and then exits instead of performing an installation", help='Generates a configuration file and then exits instead of performing an installation',
) )
parser.add_argument( parser.add_argument(
"--script", '--script',
default="guided", default='guided',
nargs="?", nargs='?',
help="Script to run for installation", help='Script to run for installation',
type=str, type=str,
) )
parser.add_argument( parser.add_argument(
"--mountpoint", '--mountpoint',
type=Path, type=Path,
nargs="?", nargs='?',
default=Path("/mnt"), default=Path('/mnt'),
help="Define an alternate mount point for installation", help='Define an alternate mount point for installation',
) )
parser.add_argument( parser.add_argument(
"--skip-ntp", '--skip-ntp',
action="store_true", action='store_true',
help="Disables NTP checks during installation", help='Disables NTP checks during installation',
default=False, default=False,
) )
parser.add_argument( parser.add_argument(
"--skip-wkd", '--skip-wkd',
action="store_true", action='store_true',
help="Disables checking if archlinux keyring wkd sync is complete.", help='Disables checking if archlinux keyring wkd sync is complete.',
default=False, default=False,
) )
parser.add_argument( parser.add_argument(
"--debug", '--debug',
action="store_true", action='store_true',
default=False, default=False,
help="Adds debug info into the log", help='Adds debug info into the log',
) )
parser.add_argument( parser.add_argument(
"--offline", '--offline',
action="store_true", action='store_true',
default=False, default=False,
help="Disabled online upstream services such as package search and key-ring auto update.", help='Disabled online upstream services such as package search and key-ring auto update.',
) )
parser.add_argument( parser.add_argument(
"--no-pkg-lookups", '--no-pkg-lookups',
action="store_true", action='store_true',
default=False, default=False,
help="Disabled package validation specifically prior to starting installation.", help='Disabled package validation specifically prior to starting installation.',
) )
parser.add_argument( parser.add_argument(
"--plugin", '--plugin',
nargs="?", nargs='?',
type=str, type=str,
default=None, default=None,
help="File path to a plugin to load", help='File path to a plugin to load',
) )
parser.add_argument( parser.add_argument(
"--skip-version-check", '--skip-version-check',
action="store_true", action='store_true',
default=False, default=False,
help="Skip the version check when running archinstall", help='Skip the version check when running archinstall',
) )
parser.add_argument( parser.add_argument(
"--advanced", '--advanced',
action="store_true", action='store_true',
default=False, default=False,
help="Enabled advanced options", help='Enabled advanced options',
) )
parser.add_argument( parser.add_argument(
"--verbose", '--verbose',
action="store_true", action='store_true',
default=False, default=False,
help="Enabled verbose options", help='Enabled verbose options',
) )
return parser return parser
@ -382,15 +382,15 @@ class ArchConfigHandler:
args.silent = False args.silent = False
if args.debug: if args.debug:
warn(f"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!") warn(f'Warning: --debug mode will write certain credentials to {storage["LOG_PATH"]}/{storage["LOG_FILE"]}!')
if args.plugin: if args.plugin:
plugin_path = Path(args.plugin) plugin_path = Path(args.plugin)
load_plugin(plugin_path) load_plugin(plugin_path)
if args.creds_decryption_key is None: if args.creds_decryption_key is None:
if os.environ.get("ARCHINSTALL_CREDS_DECRYPTION_KEY"): if os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY'):
args.creds_decryption_key = os.environ.get("ARCHINSTALL_CREDS_DECRYPTION_KEY") args.creds_decryption_key = os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY')
return args return args
@ -422,27 +422,27 @@ class ArchConfigHandler:
return config return config
def _process_creds_data(self, creds_data: str) -> dict[str, Any] | None: def _process_creds_data(self, creds_data: str) -> dict[str, Any] | None:
if creds_data.startswith("$"): # encrypted data if creds_data.startswith('$'): # encrypted data
if self._args.creds_decryption_key is not None: if self._args.creds_decryption_key is not None:
try: try:
creds_data = decrypt(creds_data, self._args.creds_decryption_key) creds_data = decrypt(creds_data, self._args.creds_decryption_key)
return json.loads(creds_data) return json.loads(creds_data)
except ValueError as err: except ValueError as err:
if "Invalid password" in str(err): if 'Invalid password' in str(err):
error(tr("Incorrect credentials file decryption password")) error(tr('Incorrect credentials file decryption password'))
exit(1) exit(1)
else: else:
debug(f"Error decrypting credentials file: {err}") debug(f'Error decrypting credentials file: {err}')
raise err from err raise err from err
else: else:
incorrect_password = False incorrect_password = False
with Tui(): with Tui():
while True: while True:
header = tr("Incorrect password") if incorrect_password else None header = tr('Incorrect password') if incorrect_password else None
decryption_pwd = get_password( decryption_pwd = get_password(
text=tr("Credentials file decryption password"), text=tr('Credentials file decryption password'),
header=header, header=header,
allow_skip=False, allow_skip=False,
skip_confirmation=True, skip_confirmation=True,
@ -455,11 +455,11 @@ class ArchConfigHandler:
creds_data = decrypt(creds_data, decryption_pwd.plaintext) creds_data = decrypt(creds_data, decryption_pwd.plaintext)
break break
except ValueError as err: except ValueError as err:
if "Invalid password" in str(err): if 'Invalid password' in str(err):
debug("Incorrect credentials file decryption password") debug('Incorrect credentials file decryption password')
incorrect_password = True incorrect_password = True
else: else:
debug(f"Error decrypting credentials file: {err}") debug(f'Error decrypting credentials file: {err}')
raise err from err raise err from err
return json.loads(creds_data) return json.loads(creds_data)
@ -467,19 +467,19 @@ class ArchConfigHandler:
def _fetch_from_url(self, url: str) -> str: def _fetch_from_url(self, url: str) -> str:
if urllib.parse.urlparse(url).scheme: if urllib.parse.urlparse(url).scheme:
try: try:
req = Request(url, headers={"User-Agent": "ArchInstall"}) req = Request(url, headers={'User-Agent': 'ArchInstall'})
with urlopen(req) as resp: with urlopen(req) as resp:
return resp.read().decode("utf-8") return resp.read().decode('utf-8')
except urllib.error.HTTPError as err: except urllib.error.HTTPError as err:
error(f"Could not fetch JSON from {url}: {err}") error(f'Could not fetch JSON from {url}: {err}')
else: else:
error("Not a valid url") error('Not a valid url')
exit(1) exit(1)
def _read_file(self, path: Path) -> str: def _read_file(self, path: Path) -> str:
if not path.exists(): if not path.exists():
error(f"Could not find file {path}") error(f'Could not find file {path}')
exit(1) exit(1)
return path.read_text() return path.read_text()

View File

@ -11,13 +11,13 @@ from .storage import storage
class Boot: class Boot:
def __init__(self, installation: Installer): def __init__(self, installation: Installer):
self.instance = installation self.instance = installation
self.container_name = "archinstall" self.container_name = 'archinstall'
self.session: SysCommandWorker | None = None self.session: SysCommandWorker | None = None
self.ready = False self.ready = False
def __enter__(self) -> "Boot": def __enter__(self) -> 'Boot':
if (existing_session := storage.get("active_boot", None)) and existing_session.instance != self.instance: if (existing_session := storage.get('active_boot', None)) and existing_session.instance != self.instance:
raise KeyError("Archinstall only supports booting up one instance and another session is already active.") raise KeyError('Archinstall only supports booting up one instance and another session is already active.')
if existing_session: if existing_session:
self.session = existing_session.session self.session = existing_session.session
@ -27,24 +27,24 @@ class Boot:
# of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual. # of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual.
self.session = SysCommandWorker( self.session = SysCommandWorker(
[ [
"systemd-nspawn", 'systemd-nspawn',
"-D", '-D',
str(self.instance.target), str(self.instance.target),
"--timezone=off", '--timezone=off',
"-b", '-b',
"--no-pager", '--no-pager',
"--machine", '--machine',
self.container_name, self.container_name,
] ]
) )
if not self.ready and self.session: if not self.ready and self.session:
while self.session.is_alive(): while self.session.is_alive():
if b" login:" in self.session: if b' login:' in self.session:
self.ready = True self.ready = True
break break
storage["active_boot"] = self storage['active_boot'] = self
return self return self
def __exit__(self, *args: str, **kwargs: str) -> None: def __exit__(self, *args: str, **kwargs: str) -> None:
@ -54,14 +54,14 @@ class Boot:
if len(args) >= 2 and args[1]: if len(args) >= 2 and args[1]:
error( error(
args[1], args[1],
f"The error above occurred in a temporary boot-up of the installation {self.instance}", f'The error above occurred in a temporary boot-up of the installation {self.instance}',
) )
shutdown = None shutdown = None
shutdown_exit_code: int | None = -1 shutdown_exit_code: int | None = -1
try: try:
shutdown = SysCommand(f"systemd-run --machine={self.container_name} --pty shutdown now") shutdown = SysCommand(f'systemd-run --machine={self.container_name} --pty shutdown now')
except SysCallError as err: except SysCallError as err:
shutdown_exit_code = err.exit_code shutdown_exit_code = err.exit_code
@ -73,12 +73,12 @@ class Boot:
shutdown_exit_code = shutdown.exit_code shutdown_exit_code = shutdown.exit_code
if self.session and (self.session.exit_code == 0 or shutdown_exit_code == 0): if self.session and (self.session.exit_code == 0 or shutdown_exit_code == 0):
storage["active_boot"] = None storage['active_boot'] = None
else: else:
session_exit_code = self.session.exit_code if self.session else -1 session_exit_code = self.session.exit_code if self.session else -1
raise SysCallError( raise SysCallError(
f"Could not shut down temporary boot of {self.instance}: {session_exit_code}/{shutdown_exit_code}", f'Could not shut down temporary boot of {self.instance}: {session_exit_code}/{shutdown_exit_code}',
exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code])), exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code])),
) )
@ -99,17 +99,17 @@ class Boot:
return self.session.is_alive() return self.session.is_alive()
def SysCommand(self, cmd: list[str], *args, **kwargs) -> SysCommand: def SysCommand(self, cmd: list[str], *args, **kwargs) -> SysCommand:
if cmd[0][0] != "/" and cmd[0][:2] != "./": if cmd[0][0] != '/' and cmd[0][:2] != './':
# This check is also done in SysCommand & SysCommandWorker. # This check is also done in SysCommand & SysCommandWorker.
# However, that check is done for `machinectl` and not for our chroot command. # However, that check is done for `machinectl` and not for our chroot command.
# So this wrapper for SysCommand will do this additionally. # So this wrapper for SysCommand will do this additionally.
cmd[0] = locate_binary(cmd[0]) cmd[0] = locate_binary(cmd[0])
return SysCommand(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs) return SysCommand(['systemd-run', f'--machine={self.container_name}', '--pty', *cmd], *args, **kwargs)
def SysCommandWorker(self, cmd: list[str], *args, **kwargs) -> SysCommandWorker: def SysCommandWorker(self, cmd: list[str], *args, **kwargs) -> SysCommandWorker:
if cmd[0][0] != "/" and cmd[0][:2] != "./": if cmd[0][0] != '/' and cmd[0][:2] != './':
cmd[0] = locate_binary(cmd[0]) cmd[0] = locate_binary(cmd[0])
return SysCommandWorker(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs) return SysCommandWorker(['systemd-run', f'--machine={self.container_name}', '--pty', *cmd], *args, **kwargs)

View File

@ -29,9 +29,9 @@ class ConfigurationOutput:
""" """
self._config = config self._config = config
self._default_save_path = storage.get("LOG_PATH", Path(".")) self._default_save_path = storage.get('LOG_PATH', Path('.'))
self._user_config_file = Path("user_configuration.json") self._user_config_file = Path('user_configuration.json')
self._user_creds_file = Path("user_credentials.json") self._user_creds_file = Path('user_credentials.json')
@property @property
def user_configuration_file(self) -> Path: def user_configuration_file(self) -> Path:
@ -50,12 +50,12 @@ class ConfigurationOutput:
return json.dumps(out, indent=4, sort_keys=True, cls=UNSAFE_JSON) return json.dumps(out, indent=4, sort_keys=True, cls=UNSAFE_JSON)
def write_debug(self) -> None: def write_debug(self) -> None:
debug(" -- Chosen configuration --") debug(' -- Chosen configuration --')
debug(self.user_config_to_json()) debug(self.user_config_to_json())
def confirm_config(self) -> bool: def confirm_config(self) -> bool:
header = f"{tr('The specified configuration will be applied')}. " header = f'{tr("The specified configuration will be applied")}. '
header += tr("Would you like to continue?") + "\n" header += tr('Would you like to continue?') + '\n'
with Tui(): with Tui():
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
@ -69,9 +69,9 @@ class ConfigurationOutput:
columns=2, columns=2,
orientation=Orientation.HORIZONTAL, orientation=Orientation.HORIZONTAL,
allow_skip=False, allow_skip=False,
preview_size="auto", preview_size='auto',
preview_style=PreviewStyle.BOTTOM, preview_style=PreviewStyle.BOTTOM,
preview_frame=FrameProperties.max(tr("Configuration")), preview_frame=FrameProperties.max(tr('Configuration')),
).run() ).run()
if result.item() != MenuItem.yes(): if result.item() != MenuItem.yes():
@ -83,8 +83,8 @@ class ConfigurationOutput:
dest_path_ok = dest_path.exists() and dest_path.is_dir() dest_path_ok = dest_path.exists() and dest_path.is_dir()
if not dest_path_ok: if not dest_path_ok:
warn( warn(
f"Destination directory {dest_path.resolve()} does not exist or is not a directory\n.", f'Destination directory {dest_path.resolve()} does not exist or is not a directory\n.',
"Configuration files can not be saved", 'Configuration files can not be saved',
) )
return dest_path_ok return dest_path_ok
@ -126,36 +126,36 @@ class ConfigurationOutput:
def save_config(config: ArchConfig) -> None: def save_config(config: ArchConfig) -> None:
def preview(item: MenuItem) -> str | None: def preview(item: MenuItem) -> str | None:
match item.value: match item.value:
case "user_config": case 'user_config':
serialized = config_output.user_config_to_json() serialized = config_output.user_config_to_json()
return f"{config_output.user_configuration_file}\n{serialized}" return f'{config_output.user_configuration_file}\n{serialized}'
case "user_creds": case 'user_creds':
if maybe_serial := config_output.user_credentials_to_json(): if maybe_serial := config_output.user_credentials_to_json():
return f"{config_output.user_credentials_file}\n{maybe_serial}" return f'{config_output.user_credentials_file}\n{maybe_serial}'
return tr("No configuration") return tr('No configuration')
case "all": case 'all':
output = [str(config_output.user_configuration_file)] output = [str(config_output.user_configuration_file)]
config_output.user_credentials_to_json() config_output.user_credentials_to_json()
output.append(str(config_output.user_credentials_file)) output.append(str(config_output.user_credentials_file))
return "\n".join(output) return '\n'.join(output)
return None return None
config_output = ConfigurationOutput(config) config_output = ConfigurationOutput(config)
items = [ items = [
MenuItem( MenuItem(
tr("Save user configuration (including disk layout)"), tr('Save user configuration (including disk layout)'),
value="user_config", value='user_config',
preview_action=preview, preview_action=preview,
), ),
MenuItem( MenuItem(
tr("Save user credentials"), tr('Save user credentials'),
value="user_creds", value='user_creds',
preview_action=preview, preview_action=preview,
), ),
MenuItem( MenuItem(
tr("Save all"), tr('Save all'),
value="all", value='all',
preview_action=preview, preview_action=preview,
), ),
] ]
@ -164,8 +164,8 @@ def save_config(config: ArchConfig) -> None:
result = SelectMenu[str]( result = SelectMenu[str](
group, group,
allow_skip=True, allow_skip=True,
preview_frame=FrameProperties.max(tr("Configuration")), preview_frame=FrameProperties.max(tr('Configuration')),
preview_size="auto", preview_size='auto',
preview_style=PreviewStyle.RIGHT, preview_style=PreviewStyle.RIGHT,
).run() ).run()
@ -175,21 +175,21 @@ def save_config(config: ArchConfig) -> None:
case ResultType.Selection: case ResultType.Selection:
save_option = result.get_value() save_option = result.get_value()
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
readline.set_completer_delims("\t\n=") readline.set_completer_delims('\t\n=')
readline.parse_and_bind("tab: complete") readline.parse_and_bind('tab: complete')
dest_path = prompt_dir( dest_path = prompt_dir(
tr("Directory"), tr('Directory'),
tr("Enter a directory for the configuration(s) to be saved (tab completion enabled)") + "\n", tr('Enter a directory for the configuration(s) to be saved (tab completion enabled)') + '\n',
allow_skip=True, allow_skip=True,
) )
if not dest_path: if not dest_path:
return return
header = tr("Do you want to save the configuration file(s) to {}?").format(dest_path) header = tr('Do you want to save the configuration file(s) to {}?').format(dest_path)
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
group.focus_item = MenuItem.yes() group.focus_item = MenuItem.yes()
@ -208,9 +208,9 @@ def save_config(config: ArchConfig) -> None:
if result.item() == MenuItem.no(): if result.item() == MenuItem.no():
return return
debug(f"Saving configuration files to {dest_path.absolute()}") debug(f'Saving configuration files to {dest_path.absolute()}')
header = tr("Do you want to encrypt the user_credentials.json file?") header = tr('Do you want to encrypt the user_credentials.json file?')
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
group.focus_item = MenuItem.no() group.focus_item = MenuItem.no()
@ -229,7 +229,7 @@ def save_config(config: ArchConfig) -> None:
case ResultType.Selection: case ResultType.Selection:
if result.item() == MenuItem.yes(): if result.item() == MenuItem.yes():
password = get_password( password = get_password(
text=tr("Credentials file encryption password"), text=tr('Credentials file encryption password'),
allow_skip=True, allow_skip=True,
) )
@ -237,9 +237,9 @@ def save_config(config: ArchConfig) -> None:
enc_password = password.plaintext enc_password = password.plaintext
match save_option: match save_option:
case "user_config": case 'user_config':
config_output.save_user_config(dest_path) config_output.save_user_config(dest_path)
case "user_creds": case 'user_creds':
config_output.save_user_creds(dest_path, password=enc_password) config_output.save_user_creds(dest_path, password=enc_password)
case "all": case 'all':
config_output.save(dest_path, creds=True, password=enc_password) config_output.save(dest_path, creds=True, password=enc_password)

View File

@ -8,7 +8,7 @@ from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
from .output import debug from .output import debug
libcrypt = ctypes.CDLL("libcrypt.so") libcrypt = ctypes.CDLL('libcrypt.so')
libcrypt.crypt.argtypes = [ctypes.c_char_p, ctypes.c_char_p] libcrypt.crypt.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
libcrypt.crypt.restype = ctypes.c_char_p libcrypt.crypt.restype = ctypes.c_char_p
@ -16,19 +16,19 @@ libcrypt.crypt.restype = ctypes.c_char_p
libcrypt.crypt_gensalt.argtypes = [ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p, ctypes.c_int] libcrypt.crypt_gensalt.argtypes = [ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p, ctypes.c_int]
libcrypt.crypt_gensalt.restype = ctypes.c_char_p libcrypt.crypt_gensalt.restype = ctypes.c_char_p
LOGIN_DEFS = Path("/etc/login.defs") LOGIN_DEFS = Path('/etc/login.defs')
def _search_login_defs(key: str) -> str | None: def _search_login_defs(key: str) -> str | None:
defs = LOGIN_DEFS.read_text() defs = LOGIN_DEFS.read_text()
for line in defs.split("\n"): for line in defs.split('\n'):
line = line.strip() line = line.strip()
if line.startswith("#"): if line.startswith('#'):
continue continue
if line.startswith(key): if line.startswith(key):
value = line.split(" ")[1] value = line.split(' ')[1]
return value return value
return None return None
@ -36,12 +36,12 @@ def _search_login_defs(key: str) -> str | None:
def crypt_gen_salt(prefix: str | bytes, rounds: int) -> bytes: def crypt_gen_salt(prefix: str | bytes, rounds: int) -> bytes:
if isinstance(prefix, str): if isinstance(prefix, str):
prefix = prefix.encode("utf-8") prefix = prefix.encode('utf-8')
setting = libcrypt.crypt_gensalt(prefix, rounds, None, 0) setting = libcrypt.crypt_gensalt(prefix, rounds, None, 0)
if setting is None: if setting is None:
raise ValueError(f"crypt_gensalt() returned NULL for prefix {prefix!r} and rounds {rounds}") raise ValueError(f'crypt_gensalt() returned NULL for prefix {prefix!r} and rounds {rounds}')
return setting return setting
@ -53,7 +53,7 @@ def crypt_yescrypt(plaintext: str) -> str:
shows that the hashing rounds are determined from YESCRYPT_COST_FACTOR in /etc/login.defs shows that the hashing rounds are determined from YESCRYPT_COST_FACTOR in /etc/login.defs
If no value was specified (or commented out) a default of 5 is choosen If no value was specified (or commented out) a default of 5 is choosen
""" """
value = _search_login_defs("YESCRYPT_COST_FACTOR") value = _search_login_defs('YESCRYPT_COST_FACTOR')
if value is not None: if value is not None:
rounds = int(value) rounds = int(value)
if rounds < 3: if rounds < 3:
@ -63,17 +63,17 @@ def crypt_yescrypt(plaintext: str) -> str:
else: else:
rounds = 5 rounds = 5
debug(f"Creating yescrypt hash with rounds {rounds}") debug(f'Creating yescrypt hash with rounds {rounds}')
enc_plaintext = plaintext.encode("utf-8") enc_plaintext = plaintext.encode('utf-8')
salt = crypt_gen_salt("$y$", rounds) salt = crypt_gen_salt('$y$', rounds)
crypt_hash = libcrypt.crypt(enc_plaintext, salt) crypt_hash = libcrypt.crypt(enc_plaintext, salt)
if crypt_hash is None: if crypt_hash is None:
raise ValueError("crypt() returned NULL") raise ValueError('crypt() returned NULL')
return crypt_hash.decode("utf-8") return crypt_hash.decode('utf-8')
def _get_fernet(salt: bytes, password: str) -> Fernet: def _get_fernet(salt: bytes, password: str) -> Fernet:
@ -90,7 +90,7 @@ def _get_fernet(salt: bytes, password: str) -> Fernet:
key = base64.urlsafe_b64encode( key = base64.urlsafe_b64encode(
kdf.derive( kdf.derive(
password.encode("utf-8"), password.encode('utf-8'),
), ),
) )
@ -100,26 +100,26 @@ def _get_fernet(salt: bytes, password: str) -> Fernet:
def encrypt(password: str, data: str) -> str: def encrypt(password: str, data: str) -> str:
salt = os.urandom(16) salt = os.urandom(16)
f = _get_fernet(salt, password) f = _get_fernet(salt, password)
token = f.encrypt(data.encode("utf-8")) token = f.encrypt(data.encode('utf-8'))
encoded_token = base64.urlsafe_b64encode(token).decode("utf-8") encoded_token = base64.urlsafe_b64encode(token).decode('utf-8')
encoded_salt = base64.urlsafe_b64encode(salt).decode("utf-8") encoded_salt = base64.urlsafe_b64encode(salt).decode('utf-8')
return f"$argon2id${encoded_salt}${encoded_token}" return f'$argon2id${encoded_salt}${encoded_token}'
def decrypt(data: str, password: str) -> str: def decrypt(data: str, password: str) -> str:
_, algo, encoded_salt, encoded_token = data.split("$") _, algo, encoded_salt, encoded_token = data.split('$')
salt = base64.urlsafe_b64decode(encoded_salt) salt = base64.urlsafe_b64decode(encoded_salt)
token = base64.urlsafe_b64decode(encoded_token) token = base64.urlsafe_b64decode(encoded_token)
if algo != "argon2id": if algo != 'argon2id':
raise ValueError(f"Unsupported algorithm {algo!r}") raise ValueError(f'Unsupported algorithm {algo!r}')
f = _get_fernet(salt, password) f = _get_fernet(salt, password)
try: try:
decrypted = f.decrypt(token) decrypted = f.decrypt(token)
except InvalidToken: except InvalidToken:
raise ValueError("Invalid password") raise ValueError('Invalid password')
return decrypted.decode("utf-8") return decrypted.decode('utf-8')

View File

@ -50,7 +50,7 @@ from .utils import (
class DeviceHandler: class DeviceHandler:
_TMP_BTRFS_MOUNT = Path("/mnt/arch_btrfs") _TMP_BTRFS_MOUNT = Path('/mnt/arch_btrfs')
def __init__(self) -> None: def __init__(self) -> None:
self._devices: dict[Path, BDevice] = {} self._devices: dict[Path, BDevice] = {}
@ -73,16 +73,16 @@ class DeviceHandler:
devices = getAllDevices() devices = getAllDevices()
devices.extend(self.get_loop_devices()) devices.extend(self.get_loop_devices())
archiso_mountpoint = Path("/run/archiso/airootfs") archiso_mountpoint = Path('/run/archiso/airootfs')
for device in devices: for device in devices:
dev_lsblk_info = find_lsblk_info(device.path, all_lsblk_info) dev_lsblk_info = find_lsblk_info(device.path, all_lsblk_info)
if not dev_lsblk_info: if not dev_lsblk_info:
debug(f"Device lsblk info not found: {device.path}") debug(f'Device lsblk info not found: {device.path}')
continue continue
if dev_lsblk_info.type == "rom": if dev_lsblk_info.type == 'rom':
continue continue
# exclude archiso loop device # exclude archiso loop device
@ -95,7 +95,7 @@ class DeviceHandler:
else: else:
disk = freshDisk(device, self.partition_table.value) disk = freshDisk(device, self.partition_table.value)
except DiskException as err: except DiskException as err:
debug(f"Unable to get disk from {device.path}: {err}") debug(f'Unable to get disk from {device.path}: {err}')
continue continue
device_info = _DeviceInfo.from_disk(disk) device_info = _DeviceInfo.from_disk(disk)
@ -105,7 +105,7 @@ class DeviceHandler:
lsblk_info = find_lsblk_info(partition.path, dev_lsblk_info.children) lsblk_info = find_lsblk_info(partition.path, dev_lsblk_info.children)
if not lsblk_info: if not lsblk_info:
debug(f"Partition lsblk info not found: {partition.path}") debug(f'Partition lsblk info not found: {partition.path}')
continue continue
fs_type = self._determine_fs_type(partition, lsblk_info) fs_type = self._determine_fs_type(partition, lsblk_info)
@ -133,20 +133,20 @@ class DeviceHandler:
devices = [] devices = []
try: try:
loop_devices = SysCommand(["losetup", "-a"]) loop_devices = SysCommand(['losetup', '-a'])
except SysCallError as err: except SysCallError as err:
debug(f"Failed to get loop devices: {err}") debug(f'Failed to get loop devices: {err}')
else: else:
for ld_info in str(loop_devices).splitlines(): for ld_info in str(loop_devices).splitlines():
try: try:
loop_device_path, _ = ld_info.split(":", maxsplit=1) loop_device_path, _ = ld_info.split(':', maxsplit=1)
except ValueError: except ValueError:
continue continue
try: try:
loop_device = getDevice(loop_device_path) loop_device = getDevice(loop_device_path)
except IOException as err: except IOException as err:
debug(f"Failed to get loop device: {err}") debug(f'Failed to get loop device: {err}')
else: else:
devices.append(loop_device) devices.append(loop_device)
@ -166,7 +166,7 @@ class DeviceHandler:
return FilesystemType(lsblk_info.fstype) if lsblk_info.fstype else None return FilesystemType(lsblk_info.fstype) if lsblk_info.fstype else None
return None return None
except ValueError: except ValueError:
debug(f"Could not determine the filesystem: {partition.fileSystem}") debug(f'Could not determine the filesystem: {partition.fileSystem}')
return None return None
@ -189,12 +189,12 @@ class DeviceHandler:
def get_parent_device_path(self, dev_path: Path) -> Path: def get_parent_device_path(self, dev_path: Path) -> Path:
lsblk = get_lsblk_info(dev_path) lsblk = get_lsblk_info(dev_path)
return Path(f"/dev/{lsblk.pkname}") return Path(f'/dev/{lsblk.pkname}')
def get_unique_path_for_device(self, dev_path: Path) -> Path | None: def get_unique_path_for_device(self, dev_path: Path) -> Path | None:
paths = Path("/dev/disk/by-id").glob("*") paths = Path('/dev/disk/by-id').glob('*')
linked_targets = {p.resolve(): p for p in paths} linked_targets = {p.resolve(): p for p in paths}
linked_wwn_targets = {p: linked_targets[p] for p in linked_targets if p.name.startswith("wwn-") or p.name.startswith("nvme-eui.")} linked_wwn_targets = {p: linked_targets[p] for p in linked_targets if p.name.startswith('wwn-') or p.name.startswith('nvme-eui.')}
if dev_path in linked_wwn_targets: if dev_path in linked_wwn_targets:
return linked_wwn_targets[dev_path] return linked_wwn_targets[dev_path]
@ -234,9 +234,9 @@ class DeviceHandler:
mountpoint = Path(common_path) mountpoint = Path(common_path)
try: try:
result = SysCommand(f"btrfs subvolume list {mountpoint}").decode() result = SysCommand(f'btrfs subvolume list {mountpoint}').decode()
except SysCallError as err: except SysCallError as err:
debug(f"Failed to read btrfs subvolume information: {err}") debug(f'Failed to read btrfs subvolume information: {err}')
return subvol_infos return subvol_infos
# It is assumed that lsblk will contain the fields as # It is assumed that lsblk will contain the fields as
@ -250,8 +250,8 @@ class DeviceHandler:
for line in result.splitlines(): for line in result.splitlines():
# expected output format: # expected output format:
# ID 257 gen 8 top level 5 path @home # ID 257 gen 8 top level 5 path @home
name = Path(line.split(" ")[-1]) name = Path(line.split(' ')[-1])
sub_vol_mountpoint = btrfs_subvol_info.get("/" / name, None) sub_vol_mountpoint = btrfs_subvol_info.get('/' / name, None)
subvol_infos.append(_BtrfsSubvolumeInfo(name, sub_vol_mountpoint)) subvol_infos.append(_BtrfsSubvolumeInfo(name, sub_vol_mountpoint))
if not lsblk_info.mountpoint: if not lsblk_info.mountpoint:
@ -272,33 +272,33 @@ class DeviceHandler:
match fs_type: match fs_type:
case FilesystemType.Btrfs | FilesystemType.F2fs | FilesystemType.Xfs: case FilesystemType.Btrfs | FilesystemType.F2fs | FilesystemType.Xfs:
# Force overwrite # Force overwrite
options.append("-f") options.append('-f')
case FilesystemType.Ext2 | FilesystemType.Ext3 | FilesystemType.Ext4: case FilesystemType.Ext2 | FilesystemType.Ext3 | FilesystemType.Ext4:
# Force create # Force create
options.append("-F") options.append('-F')
case FilesystemType.Fat12 | FilesystemType.Fat16 | FilesystemType.Fat32: case FilesystemType.Fat12 | FilesystemType.Fat16 | FilesystemType.Fat32:
mkfs_type = "fat" mkfs_type = 'fat'
# Set FAT size # Set FAT size
options.extend(("-F", fs_type.value.removeprefix(mkfs_type))) options.extend(('-F', fs_type.value.removeprefix(mkfs_type)))
case FilesystemType.Ntfs: case FilesystemType.Ntfs:
# Skip zeroing and bad sector check # Skip zeroing and bad sector check
options.append("--fast") options.append('--fast')
case FilesystemType.LinuxSwap: case FilesystemType.LinuxSwap:
command = "mkswap" command = 'mkswap'
case _: case _:
raise UnknownFilesystemFormat(f'Filetype "{fs_type.value}" is not supported') raise UnknownFilesystemFormat(f'Filetype "{fs_type.value}" is not supported')
if not command: if not command:
command = f"mkfs.{mkfs_type}" command = f'mkfs.{mkfs_type}'
cmd = [command, *options, *additional_parted_options, str(path)] cmd = [command, *options, *additional_parted_options, str(path)]
debug("Formatting filesystem:", " ".join(cmd)) debug('Formatting filesystem:', ' '.join(cmd))
try: try:
SysCommand(cmd) SysCommand(cmd)
except SysCallError as err: except SysCallError as err:
msg = f"Could not format {path} with {fs_type.value}: {err.message}" msg = f'Could not format {path} with {fs_type.value}: {err.message}'
error(msg) error(msg)
raise DiskError(msg) from err raise DiskError(msg) from err
@ -322,10 +322,10 @@ class DeviceHandler:
luks_handler.unlock(key_file=key_file) luks_handler.unlock(key_file=key_file)
if not luks_handler.mapper_dev: if not luks_handler.mapper_dev:
raise DiskError("Failed to unlock luks device") raise DiskError('Failed to unlock luks device')
if lock_after_create: if lock_after_create:
debug(f"luks2 locking device: {dev_path}") debug(f'luks2 locking device: {dev_path}')
luks_handler.lock() luks_handler.lock()
return luks_handler return luks_handler
@ -338,7 +338,7 @@ class DeviceHandler:
enc_conf: DiskEncryption, enc_conf: DiskEncryption,
) -> None: ) -> None:
if not enc_conf.encryption_password: if not enc_conf.encryption_password:
raise ValueError("No encryption password provided") raise ValueError('No encryption password provided')
luks_handler = Luks2( luks_handler = Luks2(
dev_path, dev_path,
@ -353,69 +353,69 @@ class DeviceHandler:
luks_handler.unlock(key_file=key_file) luks_handler.unlock(key_file=key_file)
if not luks_handler.mapper_dev: if not luks_handler.mapper_dev:
raise DiskError("Failed to unlock luks device") raise DiskError('Failed to unlock luks device')
info(f"luks2 formatting mapper dev: {luks_handler.mapper_dev}") info(f'luks2 formatting mapper dev: {luks_handler.mapper_dev}')
self.format(fs_type, luks_handler.mapper_dev) self.format(fs_type, luks_handler.mapper_dev)
info(f"luks2 locking device: {dev_path}") info(f'luks2 locking device: {dev_path}')
luks_handler.lock() luks_handler.lock()
def _lvm_info( def _lvm_info(
self, self,
cmd: str, cmd: str,
info_type: Literal["lv", "vg", "pvseg"], info_type: Literal['lv', 'vg', 'pvseg'],
) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None: ) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None:
raw_info = SysCommand(cmd).decode().split("\n") raw_info = SysCommand(cmd).decode().split('\n')
# for whatever reason the output sometimes contains # for whatever reason the output sometimes contains
# "File descriptor X leaked leaked on vgs invocation # "File descriptor X leaked leaked on vgs invocation
data = "\n".join([raw for raw in raw_info if "File descriptor" not in raw]) data = '\n'.join([raw for raw in raw_info if 'File descriptor' not in raw])
debug(f"LVM info: {data}") debug(f'LVM info: {data}')
reports = json.loads(data) reports = json.loads(data)
for report in reports["report"]: for report in reports['report']:
if len(report[info_type]) != 1: if len(report[info_type]) != 1:
raise ValueError("Report does not contain any entry") raise ValueError('Report does not contain any entry')
entry = report[info_type][0] entry = report[info_type][0]
match info_type: match info_type:
case "pvseg": case 'pvseg':
return LvmPVInfo( return LvmPVInfo(
pv_name=Path(entry["pv_name"]), pv_name=Path(entry['pv_name']),
lv_name=entry["lv_name"], lv_name=entry['lv_name'],
vg_name=entry["vg_name"], vg_name=entry['vg_name'],
) )
case "lv": case 'lv':
return LvmVolumeInfo( return LvmVolumeInfo(
lv_name=entry["lv_name"], lv_name=entry['lv_name'],
vg_name=entry["vg_name"], vg_name=entry['vg_name'],
lv_size=Size(int(entry["lv_size"][:-1]), Unit.B, SectorSize.default()), lv_size=Size(int(entry['lv_size'][:-1]), Unit.B, SectorSize.default()),
) )
case "vg": case 'vg':
return LvmGroupInfo( return LvmGroupInfo(
vg_uuid=entry["vg_uuid"], vg_uuid=entry['vg_uuid'],
vg_size=Size(int(entry["vg_size"][:-1]), Unit.B, SectorSize.default()), vg_size=Size(int(entry['vg_size'][:-1]), Unit.B, SectorSize.default()),
) )
return None return None
@overload @overload
def _lvm_info_with_retry(self, cmd: str, info_type: Literal["lv"]) -> LvmVolumeInfo | None: ... def _lvm_info_with_retry(self, cmd: str, info_type: Literal['lv']) -> LvmVolumeInfo | None: ...
@overload @overload
def _lvm_info_with_retry(self, cmd: str, info_type: Literal["vg"]) -> LvmGroupInfo | None: ... def _lvm_info_with_retry(self, cmd: str, info_type: Literal['vg']) -> LvmGroupInfo | None: ...
@overload @overload
def _lvm_info_with_retry(self, cmd: str, info_type: Literal["pvseg"]) -> LvmPVInfo | None: ... def _lvm_info_with_retry(self, cmd: str, info_type: Literal['pvseg']) -> LvmPVInfo | None: ...
def _lvm_info_with_retry( def _lvm_info_with_retry(
self, self,
cmd: str, cmd: str,
info_type: Literal["lv", "vg", "pvseg"], info_type: Literal['lv', 'vg', 'pvseg'],
) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None: ) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None:
while True: while True:
try: try:
@ -424,63 +424,63 @@ class DeviceHandler:
time.sleep(3) time.sleep(3)
def lvm_vol_info(self, lv_name: str) -> LvmVolumeInfo | None: def lvm_vol_info(self, lv_name: str) -> LvmVolumeInfo | None:
cmd = f"lvs --reportformat json --unit B -S lv_name={lv_name}" cmd = f'lvs --reportformat json --unit B -S lv_name={lv_name}'
return self._lvm_info_with_retry(cmd, "lv") return self._lvm_info_with_retry(cmd, 'lv')
def lvm_group_info(self, vg_name: str) -> LvmGroupInfo | None: def lvm_group_info(self, vg_name: str) -> LvmGroupInfo | None:
cmd = f"vgs --reportformat json --unit B -o vg_name,vg_uuid,vg_size -S vg_name={vg_name}" cmd = f'vgs --reportformat json --unit B -o vg_name,vg_uuid,vg_size -S vg_name={vg_name}'
return self._lvm_info_with_retry(cmd, "vg") return self._lvm_info_with_retry(cmd, 'vg')
def lvm_pvseg_info(self, vg_name: str, lv_name: str) -> LvmPVInfo | None: def lvm_pvseg_info(self, vg_name: str, lv_name: str) -> LvmPVInfo | None:
cmd = f"pvs --segments -o+lv_name,vg_name -S vg_name={vg_name},lv_name={lv_name} --reportformat json " cmd = f'pvs --segments -o+lv_name,vg_name -S vg_name={vg_name},lv_name={lv_name} --reportformat json '
return self._lvm_info_with_retry(cmd, "pvseg") return self._lvm_info_with_retry(cmd, 'pvseg')
def lvm_vol_change(self, vol: LvmVolume, activate: bool) -> None: def lvm_vol_change(self, vol: LvmVolume, activate: bool) -> None:
active_flag = "y" if activate else "n" active_flag = 'y' if activate else 'n'
cmd = f"lvchange -a {active_flag} {vol.safe_dev_path}" cmd = f'lvchange -a {active_flag} {vol.safe_dev_path}'
debug(f"lvchange volume: {cmd}") debug(f'lvchange volume: {cmd}')
SysCommand(cmd) SysCommand(cmd)
def lvm_export_vg(self, vg: LvmVolumeGroup) -> None: def lvm_export_vg(self, vg: LvmVolumeGroup) -> None:
cmd = f"vgexport {vg.name}" cmd = f'vgexport {vg.name}'
debug(f"vgexport: {cmd}") debug(f'vgexport: {cmd}')
SysCommand(cmd) SysCommand(cmd)
def lvm_import_vg(self, vg: LvmVolumeGroup) -> None: def lvm_import_vg(self, vg: LvmVolumeGroup) -> None:
cmd = f"vgimport {vg.name}" cmd = f'vgimport {vg.name}'
debug(f"vgimport: {cmd}") debug(f'vgimport: {cmd}')
SysCommand(cmd) SysCommand(cmd)
def lvm_vol_reduce(self, vol_path: Path, amount: Size) -> None: def lvm_vol_reduce(self, vol_path: Path, amount: Size) -> None:
val = amount.format_size(Unit.B, include_unit=False) val = amount.format_size(Unit.B, include_unit=False)
cmd = f"lvreduce -L -{val}B {vol_path}" cmd = f'lvreduce -L -{val}B {vol_path}'
debug(f"Reducing LVM volume size: {cmd}") debug(f'Reducing LVM volume size: {cmd}')
SysCommand(cmd) SysCommand(cmd)
def lvm_pv_create(self, pvs: Iterable[Path]) -> None: def lvm_pv_create(self, pvs: Iterable[Path]) -> None:
cmd = "pvcreate " + " ".join([str(pv) for pv in pvs]) cmd = 'pvcreate ' + ' '.join([str(pv) for pv in pvs])
debug(f"Creating LVM PVS: {cmd}") debug(f'Creating LVM PVS: {cmd}')
worker = SysCommandWorker(cmd) worker = SysCommandWorker(cmd)
worker.poll() worker.poll()
worker.write(b"y\n", line_ending=False) worker.write(b'y\n', line_ending=False)
def lvm_vg_create(self, pvs: Iterable[Path], vg_name: str) -> None: def lvm_vg_create(self, pvs: Iterable[Path], vg_name: str) -> None:
pvs_str = " ".join([str(pv) for pv in pvs]) pvs_str = ' '.join([str(pv) for pv in pvs])
cmd = f"vgcreate --yes {vg_name} {pvs_str}" cmd = f'vgcreate --yes {vg_name} {pvs_str}'
debug(f"Creating LVM group: {cmd}") debug(f'Creating LVM group: {cmd}')
worker = SysCommandWorker(cmd) worker = SysCommandWorker(cmd)
worker.poll() worker.poll()
worker.write(b"y\n", line_ending=False) worker.write(b'y\n', line_ending=False)
def lvm_vol_create(self, vg_name: str, volume: LvmVolume, offset: Size | None = None) -> None: def lvm_vol_create(self, vg_name: str, volume: LvmVolume, offset: Size | None = None) -> None:
if offset is not None: if offset is not None:
@ -489,16 +489,16 @@ class DeviceHandler:
length = volume.length length = volume.length
length_str = length.format_size(Unit.B, include_unit=False) length_str = length.format_size(Unit.B, include_unit=False)
cmd = f"lvcreate --yes -L {length_str}B {vg_name} -n {volume.name}" cmd = f'lvcreate --yes -L {length_str}B {vg_name} -n {volume.name}'
debug(f"Creating volume: {cmd}") debug(f'Creating volume: {cmd}')
worker = SysCommandWorker(cmd) worker = SysCommandWorker(cmd)
worker.poll() worker.poll()
worker.write(b"y\n", line_ending=False) worker.write(b'y\n', line_ending=False)
volume.vg_name = vg_name volume.vg_name = vg_name
volume.dev_path = Path(f"/dev/{vg_name}/{volume.name}") volume.dev_path = Path(f'/dev/{vg_name}/{volume.name}')
def _setup_partition( def _setup_partition(
self, self,
@ -510,11 +510,11 @@ class DeviceHandler:
# when we require a delete and the partition to be (re)created # when we require a delete and the partition to be (re)created
# already exists then we have to delete it first # already exists then we have to delete it first
if requires_delete and part_mod.status in [ModificationStatus.Modify, ModificationStatus.Delete]: if requires_delete and part_mod.status in [ModificationStatus.Modify, ModificationStatus.Delete]:
info(f"Delete existing partition: {part_mod.safe_dev_path}") info(f'Delete existing partition: {part_mod.safe_dev_path}')
part_info = self.find_partition(part_mod.safe_dev_path) part_info = self.find_partition(part_mod.safe_dev_path)
if not part_info: if not part_info:
raise DiskError(f"No partition for dev path found: {part_mod.safe_dev_path}") raise DiskError(f'No partition for dev path found: {part_mod.safe_dev_path}')
disk.deletePartition(part_info.partition) disk.deletePartition(part_info.partition)
@ -550,14 +550,14 @@ class DeviceHandler:
for flag in part_mod.flags: for flag in part_mod.flags:
partition.setFlag(flag.flag_id) partition.setFlag(flag.flag_id)
debug(f"\tType: {part_mod.type.value}") debug(f'\tType: {part_mod.type.value}')
debug(f"\tFilesystem: {fs_value}") debug(f'\tFilesystem: {fs_value}')
debug(f"\tGeometry: {start_sector.value} start sector, {length_sector.value} length") debug(f'\tGeometry: {start_sector.value} start sector, {length_sector.value} length')
try: try:
disk.addPartition(partition=partition, constraint=disk.device.optimalAlignedConstraint) disk.addPartition(partition=partition, constraint=disk.device.optimalAlignedConstraint)
except PartitionException as ex: except PartitionException as ex:
raise DiskError(f"Unable to add partition, most likely due to overlapping sectors: {ex}") from ex raise DiskError(f'Unable to add partition, most likely due to overlapping sectors: {ex}') from ex
if disk.type == PartitionTable.GPT.value: if disk.type == PartitionTable.GPT.value:
if part_mod.is_root(): if part_mod.is_root():
@ -572,18 +572,18 @@ class DeviceHandler:
lsblk_info = get_lsblk_info(path) lsblk_info = get_lsblk_info(path)
if not lsblk_info.partn: if not lsblk_info.partn:
debug(f"Unable to determine new partition number: {path}\n{lsblk_info}") debug(f'Unable to determine new partition number: {path}\n{lsblk_info}')
raise DiskError(f"Unable to determine new partition number: {path}") raise DiskError(f'Unable to determine new partition number: {path}')
if not lsblk_info.partuuid: if not lsblk_info.partuuid:
debug(f"Unable to determine new partition uuid: {path}\n{lsblk_info}") debug(f'Unable to determine new partition uuid: {path}\n{lsblk_info}')
raise DiskError(f"Unable to determine new partition uuid: {path}") raise DiskError(f'Unable to determine new partition uuid: {path}')
if not lsblk_info.uuid: if not lsblk_info.uuid:
debug(f"Unable to determine new uuid: {path}\n{lsblk_info}") debug(f'Unable to determine new uuid: {path}\n{lsblk_info}')
raise DiskError(f"Unable to determine new uuid: {path}") raise DiskError(f'Unable to determine new uuid: {path}')
debug(f"partition information found: {lsblk_info.model_dump_json()}") debug(f'partition information found: {lsblk_info.model_dump_json()}')
return lsblk_info return lsblk_info
@ -593,28 +593,28 @@ class DeviceHandler:
btrfs_subvols: list[SubvolumeModification], btrfs_subvols: list[SubvolumeModification],
mount_options: list[str], mount_options: list[str],
) -> None: ) -> None:
info(f"Creating subvolumes: {path}") info(f'Creating subvolumes: {path}')
self.mount(path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True) self.mount(path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True)
for sub_vol in sorted(btrfs_subvols, key=lambda x: x.name): for sub_vol in sorted(btrfs_subvols, key=lambda x: x.name):
debug(f"Creating subvolume: {sub_vol.name}") debug(f'Creating subvolume: {sub_vol.name}')
subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name
SysCommand(f"btrfs subvolume create -p {subvol_path}") SysCommand(f'btrfs subvolume create -p {subvol_path}')
if BtrfsMountOption.nodatacow.value in mount_options: if BtrfsMountOption.nodatacow.value in mount_options:
try: try:
SysCommand(f"chattr +C {subvol_path}") SysCommand(f'chattr +C {subvol_path}')
except SysCallError as err: except SysCallError as err:
raise DiskError(f"Could not set nodatacow attribute at {subvol_path}: {err}") raise DiskError(f'Could not set nodatacow attribute at {subvol_path}: {err}')
if BtrfsMountOption.compress.value in mount_options: if BtrfsMountOption.compress.value in mount_options:
try: try:
SysCommand(f"chattr +c {subvol_path}") SysCommand(f'chattr +c {subvol_path}')
except SysCallError as err: except SysCallError as err:
raise DiskError(f"Could not set compress attribute at {subvol_path}: {err}") raise DiskError(f'Could not set compress attribute at {subvol_path}: {err}')
umount(path) umount(path)
@ -623,12 +623,12 @@ class DeviceHandler:
part_mod: PartitionModification, part_mod: PartitionModification,
enc_conf: DiskEncryption | None = None, enc_conf: DiskEncryption | None = None,
) -> None: ) -> None:
info(f"Creating subvolumes: {part_mod.safe_dev_path}") info(f'Creating subvolumes: {part_mod.safe_dev_path}')
# unlock the partition first if it's encrypted # unlock the partition first if it's encrypted
if enc_conf is not None and part_mod in enc_conf.partitions: if enc_conf is not None and part_mod in enc_conf.partitions:
if not part_mod.mapper_name: if not part_mod.mapper_name:
raise ValueError("No device path specified for modification") raise ValueError('No device path specified for modification')
luks_handler = self.unlock_luks2_dev( luks_handler = self.unlock_luks2_dev(
part_mod.safe_dev_path, part_mod.safe_dev_path,
@ -637,7 +637,7 @@ class DeviceHandler:
) )
if not luks_handler.mapper_dev: if not luks_handler.mapper_dev:
raise DiskError("Failed to unlock luks device") raise DiskError('Failed to unlock luks device')
dev_path = luks_handler.mapper_dev dev_path = luks_handler.mapper_dev
else: else:
@ -652,11 +652,11 @@ class DeviceHandler:
) )
for sub_vol in sorted(part_mod.btrfs_subvols, key=lambda x: x.name): for sub_vol in sorted(part_mod.btrfs_subvols, key=lambda x: x.name):
debug(f"Creating subvolume: {sub_vol.name}") debug(f'Creating subvolume: {sub_vol.name}')
subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name
SysCommand(f"btrfs subvolume create -p {subvol_path}") SysCommand(f'btrfs subvolume create -p {subvol_path}')
umount(dev_path) umount(dev_path)
@ -675,17 +675,17 @@ class DeviceHandler:
luks_handler.unlock() luks_handler.unlock()
if not luks_handler.is_unlocked(): if not luks_handler.is_unlocked():
raise DiskError(f"Failed to unlock luks2 device: {dev_path}") raise DiskError(f'Failed to unlock luks2 device: {dev_path}')
return luks_handler return luks_handler
def umount_all_existing(self, device_path: Path) -> None: def umount_all_existing(self, device_path: Path) -> None:
debug(f"Unmounting all existing partitions: {device_path}") debug(f'Unmounting all existing partitions: {device_path}')
existing_partitions = self._devices[device_path].partition_infos existing_partitions = self._devices[device_path].partition_infos
for partition in existing_partitions: for partition in existing_partitions:
debug(f"Unmounting: {partition.path}") debug(f'Unmounting: {partition.path}')
# un-mount for existing encrypted partitions # un-mount for existing encrypted partitions
if partition.fs_type == FilesystemType.Crypto_luks: if partition.fs_type == FilesystemType.Crypto_luks:
@ -706,15 +706,15 @@ class DeviceHandler:
# WARNING: the entire device will be wiped and all data lost # WARNING: the entire device will be wiped and all data lost
if modification.wipe: if modification.wipe:
if partition_table.is_mbr() and len(modification.partitions) > 3: if partition_table.is_mbr() and len(modification.partitions) > 3:
raise DiskError("Too many partitions on disk, MBR disks can only have 3 primary partitions") raise DiskError('Too many partitions on disk, MBR disks can only have 3 primary partitions')
self.wipe_dev(modification.device) self.wipe_dev(modification.device)
disk = freshDisk(modification.device.disk.device, partition_table.value) disk = freshDisk(modification.device.disk.device, partition_table.value)
else: else:
info(f"Use existing device: {modification.device_path}") info(f'Use existing device: {modification.device_path}')
disk = modification.device.disk disk = modification.device.disk
info(f"Creating partitions: {modification.device_path}") info(f'Creating partitions: {modification.device_path}')
# don't touch existing partitions # don't touch existing partitions
filtered_part = [p for p in modification.partitions if not p.exists()] filtered_part = [p for p in modification.partitions if not p.exists()]
@ -730,9 +730,9 @@ class DeviceHandler:
@staticmethod @staticmethod
def swapon(path: Path) -> None: def swapon(path: Path) -> None:
try: try:
SysCommand(["swapon", str(path)]) SysCommand(['swapon', str(path)])
except SysCallError as err: except SysCallError as err:
raise DiskError(f"Could not enable swap {path}:\n{err.message}") raise DiskError(f'Could not enable swap {path}:\n{err.message}')
def mount( def mount(
self, self,
@ -746,30 +746,30 @@ class DeviceHandler:
target_mountpoint.mkdir(parents=True, exist_ok=True) target_mountpoint.mkdir(parents=True, exist_ok=True)
if not target_mountpoint.exists(): if not target_mountpoint.exists():
raise ValueError("Target mountpoint does not exist") raise ValueError('Target mountpoint does not exist')
lsblk_info = get_lsblk_info(dev_path) lsblk_info = get_lsblk_info(dev_path)
if target_mountpoint in lsblk_info.mountpoints: if target_mountpoint in lsblk_info.mountpoints:
info(f"Device already mounted at {target_mountpoint}") info(f'Device already mounted at {target_mountpoint}')
return return
cmd = ["mount"] cmd = ['mount']
if len(options): if len(options):
cmd.extend(("-o", ",".join(options))) cmd.extend(('-o', ','.join(options)))
if mount_fs: if mount_fs:
cmd.extend(("-t", mount_fs)) cmd.extend(('-t', mount_fs))
cmd.extend((str(dev_path), str(target_mountpoint))) cmd.extend((str(dev_path), str(target_mountpoint)))
command = " ".join(cmd) command = ' '.join(cmd)
debug(f"Mounting {dev_path}: {command}") debug(f'Mounting {dev_path}: {command}')
try: try:
SysCommand(command) SysCommand(command)
except SysCallError as err: except SysCallError as err:
raise DiskError(f"Could not mount {dev_path}: {command}\n{err.message}") raise DiskError(f'Could not mount {dev_path}: {command}\n{err.message}')
def detect_pre_mounted_mods(self, base_mountpoint: Path) -> list[DeviceModification]: def detect_pre_mounted_mods(self, base_mountpoint: Path) -> list[DeviceModification]:
part_mods: dict[Path, list[PartitionModification]] = {} part_mods: dict[Path, list[PartitionModification]] = {}
@ -799,16 +799,16 @@ class DeviceHandler:
def partprobe(self, path: Path | None = None) -> None: def partprobe(self, path: Path | None = None) -> None:
if path is not None: if path is not None:
command = f"partprobe {path}" command = f'partprobe {path}'
else: else:
command = "partprobe" command = 'partprobe'
try: try:
debug(f"Calling partprobe: {command}") debug(f'Calling partprobe: {command}')
SysCommand(command) SysCommand(command)
except SysCallError as err: except SysCallError as err:
if "have been written, but we have been unable to inform the kernel of the change" in str(err): if 'have been written, but we have been unable to inform the kernel of the change' in str(err):
log(f"Partprobe was not able to inform the kernel of the new disk state (ignoring error): {err}", fg="gray", level=logging.INFO) log(f'Partprobe was not able to inform the kernel of the new disk state (ignoring error): {err}', fg='gray', level=logging.INFO)
else: else:
error(f'"{command}" failed to run (continuing anyway): {err}') error(f'"{command}" failed to run (continuing anyway): {err}')
@ -818,7 +818,7 @@ class DeviceHandler:
@param dev_path: Device path of the partition to be wiped. @param dev_path: Device path of the partition to be wiped.
@type dev_path: str @type dev_path: str
""" """
with open(dev_path, "wb") as p: with open(dev_path, 'wb') as p:
p.write(bytearray(1024)) p.write(bytearray(1024))
def wipe_dev(self, block_device: BDevice) -> None: def wipe_dev(self, block_device: BDevice) -> None:
@ -827,7 +827,7 @@ class DeviceHandler:
This is not intended to be secure, but rather to ensure that This is not intended to be secure, but rather to ensure that
auto-discovery tools don't recognize anything here. auto-discovery tools don't recognize anything here.
""" """
info(f"Wiping partitions and metadata: {block_device.device_info.path}") info(f'Wiping partitions and metadata: {block_device.device_info.path}')
for partition in block_device.partition_infos: for partition in block_device.partition_infos:
luks = Luks2(partition.path) luks = Luks2(partition.path)
@ -841,9 +841,9 @@ class DeviceHandler:
@staticmethod @staticmethod
def udev_sync() -> None: def udev_sync() -> None:
try: try:
SysCommand("udevadm settle") SysCommand('udevadm settle')
except SysCallError as err: except SysCallError as err:
debug(f"Failed to synchronize with udev: {err}") debug(f'Failed to synchronize with udev: {err}')
device_handler = DeviceHandler() device_handler = DeviceHandler()

View File

@ -38,19 +38,19 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
def _define_menu_options(self) -> list[MenuItem]: def _define_menu_options(self) -> list[MenuItem]:
return [ return [
MenuItem( MenuItem(
text=tr("Partitioning"), text=tr('Partitioning'),
action=self._select_disk_layout_config, action=self._select_disk_layout_config,
value=self._disk_menu_config.disk_config, value=self._disk_menu_config.disk_config,
preview_action=self._prev_disk_layouts, preview_action=self._prev_disk_layouts,
key="disk_config", key='disk_config',
), ),
MenuItem( MenuItem(
text="LVM (BETA)", text='LVM (BETA)',
action=self._select_lvm_config, action=self._select_lvm_config,
value=self._disk_menu_config.lvm_config, value=self._disk_menu_config.lvm_config,
preview_action=self._prev_lvm_config, preview_action=self._prev_lvm_config,
dependencies=[self._check_dep_lvm], dependencies=[self._check_dep_lvm],
key="lvm_config", key='lvm_config',
), ),
] ]
@ -65,7 +65,7 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
return None return None
def _check_dep_lvm(self) -> bool: def _check_dep_lvm(self) -> bool:
disk_layout_conf: DiskLayoutConfiguration | None = self._menu_item_group.find_by_key("disk_config").value disk_layout_conf: DiskLayoutConfiguration | None = self._menu_item_group.find_by_key('disk_config').value
if disk_layout_conf and disk_layout_conf.config_type == DiskLayoutType.Default: if disk_layout_conf and disk_layout_conf.config_type == DiskLayoutType.Default:
return True return True
@ -79,12 +79,12 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
disk_config = select_disk_config(preset) disk_config = select_disk_config(preset)
if disk_config != preset: if disk_config != preset:
self._menu_item_group.find_by_key("lvm_config").value = None self._menu_item_group.find_by_key('lvm_config').value = None
return disk_config return disk_config
def _select_lvm_config(self, preset: LvmConfiguration | None) -> LvmConfiguration | None: def _select_lvm_config(self, preset: LvmConfiguration | None) -> LvmConfiguration | None:
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key("disk_config").value disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
if disk_config: if disk_config:
return select_lvm_config(disk_config, preset=preset) return select_lvm_config(disk_config, preset=preset)
@ -98,28 +98,28 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
disk_layout_conf = item.get_value() disk_layout_conf = item.get_value()
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount: if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
msg = tr("Configuration type: {}").format(disk_layout_conf.config_type.display_msg()) + "\n" msg = tr('Configuration type: {}').format(disk_layout_conf.config_type.display_msg()) + '\n'
msg += tr("Mountpoint") + ": " + str(disk_layout_conf.mountpoint) msg += tr('Mountpoint') + ': ' + str(disk_layout_conf.mountpoint)
return msg return msg
device_mods = [d for d in disk_layout_conf.device_modifications if d.partitions] device_mods = [d for d in disk_layout_conf.device_modifications if d.partitions]
if device_mods: if device_mods:
output_partition = "{}: {}\n".format(tr("Configuration"), disk_layout_conf.config_type.display_msg()) output_partition = '{}: {}\n'.format(tr('Configuration'), disk_layout_conf.config_type.display_msg())
output_btrfs = "" output_btrfs = ''
for mod in device_mods: for mod in device_mods:
# create partition table # create partition table
partition_table = FormattedOutput.as_table(mod.partitions) partition_table = FormattedOutput.as_table(mod.partitions)
output_partition += f"{mod.device_path}: {mod.device.device_info.model}\n" output_partition += f'{mod.device_path}: {mod.device.device_info.model}\n'
output_partition += "{}: {}\n".format(tr("Wipe"), mod.wipe) output_partition += '{}: {}\n'.format(tr('Wipe'), mod.wipe)
output_partition += partition_table + "\n" output_partition += partition_table + '\n'
# create btrfs table # create btrfs table
btrfs_partitions = [p for p in mod.partitions if p.btrfs_subvols] btrfs_partitions = [p for p in mod.partitions if p.btrfs_subvols]
for partition in btrfs_partitions: for partition in btrfs_partitions:
output_btrfs += FormattedOutput.as_table(partition.btrfs_subvols) + "\n" output_btrfs += FormattedOutput.as_table(partition.btrfs_subvols) + '\n'
output = output_partition + output_btrfs output = output_partition + output_btrfs
return output.rstrip() return output.rstrip()
@ -132,16 +132,16 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
lvm_config: LvmConfiguration = item.value lvm_config: LvmConfiguration = item.value
output = "{}: {}\n".format(tr("Configuration"), lvm_config.config_type.display_msg()) output = '{}: {}\n'.format(tr('Configuration'), lvm_config.config_type.display_msg())
for vol_gp in lvm_config.vol_groups: for vol_gp in lvm_config.vol_groups:
pv_table = FormattedOutput.as_table(vol_gp.pvs) pv_table = FormattedOutput.as_table(vol_gp.pvs)
output += "{}:\n{}".format(tr("Physical volumes"), pv_table) output += '{}:\n{}'.format(tr('Physical volumes'), pv_table)
output += f"\nVolume Group: {vol_gp.name}" output += f'\nVolume Group: {vol_gp.name}'
lvm_volumes = FormattedOutput.as_table(vol_gp.volumes) lvm_volumes = FormattedOutput.as_table(vol_gp.volumes)
output += "\n\n{}:\n{}".format(tr("Volumes"), lvm_volumes) output += '\n\n{}:\n{}'.format(tr('Volumes'), lvm_volumes)
return output return output

View File

@ -50,43 +50,43 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
def _define_menu_options(self) -> list[MenuItem]: def _define_menu_options(self) -> list[MenuItem]:
return [ return [
MenuItem( MenuItem(
text=tr("Encryption type"), text=tr('Encryption type'),
action=lambda x: select_encryption_type(self._disk_config, x), action=lambda x: select_encryption_type(self._disk_config, x),
value=self._enc_config.encryption_type, value=self._enc_config.encryption_type,
preview_action=self._preview, preview_action=self._preview,
key="encryption_type", key='encryption_type',
), ),
MenuItem( MenuItem(
text=tr("Encryption password"), text=tr('Encryption password'),
action=lambda x: select_encrypted_password(), action=lambda x: select_encrypted_password(),
value=self._enc_config.encryption_password, value=self._enc_config.encryption_password,
dependencies=[self._check_dep_enc_type], dependencies=[self._check_dep_enc_type],
preview_action=self._preview, preview_action=self._preview,
key="encryption_password", key='encryption_password',
), ),
MenuItem( MenuItem(
text=tr("Partitions"), text=tr('Partitions'),
action=lambda x: select_partitions_to_encrypt(self._disk_config.device_modifications, x), action=lambda x: select_partitions_to_encrypt(self._disk_config.device_modifications, x),
value=self._enc_config.partitions, value=self._enc_config.partitions,
dependencies=[self._check_dep_partitions], dependencies=[self._check_dep_partitions],
preview_action=self._preview, preview_action=self._preview,
key="partitions", key='partitions',
), ),
MenuItem( MenuItem(
text=tr("LVM volumes"), text=tr('LVM volumes'),
action=self._select_lvm_vols, action=self._select_lvm_vols,
value=self._enc_config.lvm_volumes, value=self._enc_config.lvm_volumes,
dependencies=[self._check_dep_lvm_vols], dependencies=[self._check_dep_lvm_vols],
preview_action=self._preview, preview_action=self._preview,
key="lvm_volumes", key='lvm_volumes',
), ),
MenuItem( MenuItem(
text=tr("HSM"), text=tr('HSM'),
action=select_hsm, action=select_hsm,
value=self._enc_config.hsm_device, value=self._enc_config.hsm_device,
dependencies=[self._check_dep_enc_type], dependencies=[self._check_dep_enc_type],
preview_action=self._preview, preview_action=self._preview,
key="hsm_device", key='hsm_device',
), ),
] ]
@ -96,19 +96,19 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
return [] return []
def _check_dep_enc_type(self) -> bool: def _check_dep_enc_type(self) -> bool:
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
if enc_type and enc_type != EncryptionType.NoEncryption: if enc_type and enc_type != EncryptionType.NoEncryption:
return True return True
return False return False
def _check_dep_partitions(self) -> bool: def _check_dep_partitions(self) -> bool:
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
if enc_type and enc_type in [EncryptionType.Luks, EncryptionType.LvmOnLuks]: if enc_type and enc_type in [EncryptionType.Luks, EncryptionType.LvmOnLuks]:
return True return True
return False return False
def _check_dep_lvm_vols(self) -> bool: def _check_dep_lvm_vols(self) -> bool:
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
if enc_type and enc_type == EncryptionType.LuksOnLvm: if enc_type and enc_type == EncryptionType.LuksOnLvm:
return True return True
return False return False
@ -117,10 +117,10 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
def run(self) -> DiskEncryption | None: def run(self) -> DiskEncryption | None:
super().run() super().run()
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
enc_password: Password | None = self._item_group.find_by_key("encryption_password").value enc_password: Password | None = self._item_group.find_by_key('encryption_password').value
enc_partitions = self._item_group.find_by_key("partitions").value enc_partitions = self._item_group.find_by_key('partitions').value
enc_lvm_vols = self._item_group.find_by_key("lvm_volumes").value enc_lvm_vols = self._item_group.find_by_key('lvm_volumes').value
assert enc_type is not None assert enc_type is not None
assert enc_partitions is not None assert enc_partitions is not None
@ -144,22 +144,22 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
return None return None
def _preview(self, item: MenuItem) -> str | None: def _preview(self, item: MenuItem) -> str | None:
output = "" output = ''
if (enc_type := self._prev_type()) is not None: if (enc_type := self._prev_type()) is not None:
output += enc_type output += enc_type
if (enc_pwd := self._prev_password()) is not None: if (enc_pwd := self._prev_password()) is not None:
output += f"\n{enc_pwd}" output += f'\n{enc_pwd}'
if (fido_device := self._prev_hsm()) is not None: if (fido_device := self._prev_hsm()) is not None:
output += f"\n{fido_device}" output += f'\n{fido_device}'
if (partitions := self._prev_partitions()) is not None: if (partitions := self._prev_partitions()) is not None:
output += f"\n\n{partitions}" output += f'\n\n{partitions}'
if (lvm := self._prev_lvm_vols()) is not None: if (lvm := self._prev_lvm_vols()) is not None:
output += f"\n\n{lvm}" output += f'\n\n{lvm}'
if not output: if not output:
return None return None
@ -167,51 +167,51 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
return output return output
def _prev_type(self) -> str | None: def _prev_type(self) -> str | None:
enc_type = self._item_group.find_by_key("encryption_type").value enc_type = self._item_group.find_by_key('encryption_type').value
if enc_type: if enc_type:
enc_text = EncryptionType.type_to_text(enc_type) enc_text = EncryptionType.type_to_text(enc_type)
return f"{tr('Encryption type')}: {enc_text}" return f'{tr("Encryption type")}: {enc_text}'
return None return None
def _prev_password(self) -> str | None: def _prev_password(self) -> str | None:
enc_pwd = self._item_group.find_by_key("encryption_password").value enc_pwd = self._item_group.find_by_key('encryption_password').value
if enc_pwd: if enc_pwd:
return f"{tr('Encryption password')}: {enc_pwd.hidden()}" return f'{tr("Encryption password")}: {enc_pwd.hidden()}'
return None return None
def _prev_partitions(self) -> str | None: def _prev_partitions(self) -> str | None:
partitions: list[PartitionModification] | None = self._item_group.find_by_key("partitions").value partitions: list[PartitionModification] | None = self._item_group.find_by_key('partitions').value
if partitions: if partitions:
output = tr("Partitions to be encrypted") + "\n" output = tr('Partitions to be encrypted') + '\n'
output += FormattedOutput.as_table(partitions) output += FormattedOutput.as_table(partitions)
return output.rstrip() return output.rstrip()
return None return None
def _prev_lvm_vols(self) -> str | None: def _prev_lvm_vols(self) -> str | None:
volumes: list[PartitionModification] | None = self._item_group.find_by_key("lvm_volumes").value volumes: list[PartitionModification] | None = self._item_group.find_by_key('lvm_volumes').value
if volumes: if volumes:
output = tr("LVM volumes to be encrypted") + "\n" output = tr('LVM volumes to be encrypted') + '\n'
output += FormattedOutput.as_table(volumes) output += FormattedOutput.as_table(volumes)
return output.rstrip() return output.rstrip()
return None return None
def _prev_hsm(self) -> str | None: def _prev_hsm(self) -> str | None:
fido_device: Fido2Device | None = self._item_group.find_by_key("hsm_device").value fido_device: Fido2Device | None = self._item_group.find_by_key('hsm_device').value
if not fido_device: if not fido_device:
return None return None
output = str(fido_device.path) output = str(fido_device.path)
output += f" ({fido_device.manufacturer}, {fido_device.product})" output += f' ({fido_device.manufacturer}, {fido_device.product})'
return f"{tr('HSM device')}: {output}" return f'{tr("HSM device")}: {output}'
def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: EncryptionType) -> EncryptionType | None: def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: EncryptionType) -> EncryptionType | None:
@ -232,7 +232,7 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt
allow_skip=True, allow_skip=True,
allow_reset=True, allow_reset=True,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Encryption type")), frame=FrameProperties.min(tr('Encryption type')),
).run() ).run()
match result.type_: match result.type_:
@ -245,9 +245,9 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt
def select_encrypted_password() -> Password | None: def select_encrypted_password() -> Password | None:
header = tr("Enter disk encryption password (leave blank for no encryption)") + "\n" header = tr('Enter disk encryption password (leave blank for no encryption)') + '\n'
password = get_password( password = get_password(
text=tr("Disk encryption password"), text=tr('Disk encryption password'),
header=header, header=header,
allow_skip=True, allow_skip=True,
) )
@ -256,7 +256,7 @@ def select_encrypted_password() -> Password | None:
def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None: def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
header = tr("Select a FIDO2 device to use for HSM") + "\n" header = tr('Select a FIDO2 device to use for HSM') + '\n'
try: try:
fido_devices = Fido2.get_fido2_devices() fido_devices = Fido2.get_fido2_devices()
@ -292,7 +292,7 @@ def select_partitions_to_encrypt(
# do not allow encrypting the boot partition # do not allow encrypting the boot partition
for mod in modification: for mod in modification:
partitions += [p for p in mod.partitions if p.mountpoint != Path("/boot") and not p.is_swap()] partitions += [p for p in mod.partitions if p.mountpoint != Path('/boot') and not p.is_swap()]
# do not allow encrypting existing partitions that are not marked as wipe # do not allow encrypting existing partitions that are not marked as wipe
avail_partitions = [p for p in partitions if not p.exists()] avail_partitions = [p for p in partitions if not p.exists()]

View File

@ -40,10 +40,10 @@ class Fido2:
# down moving the cursor in the menu # down moving the cursor in the menu
if not cls._loaded or reload: if not cls._loaded or reload:
try: try:
ret = SysCommand("systemd-cryptenroll --fido2-device=list").decode() ret = SysCommand('systemd-cryptenroll --fido2-device=list').decode()
except SysCallError: except SysCallError:
error("fido2 support is most likely not installed") error('fido2 support is most likely not installed')
raise ValueError("HSM devices can not be detected, is libfido2 installed?") raise ValueError('HSM devices can not be detected, is libfido2 installed?')
fido_devices = clear_vt100_escape_codes_from_str(ret) fido_devices = clear_vt100_escape_codes_from_str(ret)
@ -51,10 +51,10 @@ class Fido2:
product_pos = 0 product_pos = 0
devices = [] devices = []
for line in fido_devices.split("\r\n"): for line in fido_devices.split('\r\n'):
if "/dev" not in line: if '/dev' not in line:
manufacturer_pos = line.find("MANUFACTURER") manufacturer_pos = line.find('MANUFACTURER')
product_pos = line.find("PRODUCT") product_pos = line.find('PRODUCT')
continue continue
path = line[:manufacturer_pos].rstrip() path = line[:manufacturer_pos].rstrip()
@ -77,18 +77,18 @@ class Fido2:
dev_path: Path, dev_path: Path,
password: Password, password: Password,
) -> None: ) -> None:
worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device.path} {dev_path}", peek_output=True) worker = SysCommandWorker(f'systemd-cryptenroll --fido2-device={hsm_device.path} {dev_path}', peek_output=True)
pw_inputted = False pw_inputted = False
pin_inputted = False pin_inputted = False
while worker.is_alive(): while worker.is_alive():
if pw_inputted is False: if pw_inputted is False:
if bytes(f"please enter current passphrase for disk {dev_path}", "UTF-8") in worker._trace_log.lower(): if bytes(f'please enter current passphrase for disk {dev_path}', 'UTF-8') in worker._trace_log.lower():
worker.write(bytes(password.plaintext, "UTF-8")) worker.write(bytes(password.plaintext, 'UTF-8'))
pw_inputted = True pw_inputted = True
elif pin_inputted is False: elif pin_inputted is False:
if bytes("please enter security token pin", "UTF-8") in worker._trace_log.lower(): if bytes('please enter security token pin', 'UTF-8') in worker._trace_log.lower():
worker.write(bytes(getpass.getpass(" "), "UTF-8")) worker.write(bytes(getpass.getpass(' '), 'UTF-8'))
pin_inputted = True pin_inputted = True
info("You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds") info('You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds')

View File

@ -37,16 +37,16 @@ class FilesystemHandler:
def perform_filesystem_operations(self, show_countdown: bool = True) -> None: def perform_filesystem_operations(self, show_countdown: bool = True) -> None:
if self._disk_config.config_type == DiskLayoutType.Pre_mount: if self._disk_config.config_type == DiskLayoutType.Pre_mount:
debug("Disk layout configuration is set to pre-mount, not performing any operations") debug('Disk layout configuration is set to pre-mount, not performing any operations')
return return
device_mods = [d for d in self._disk_config.device_modifications if d.partitions] device_mods = [d for d in self._disk_config.device_modifications if d.partitions]
if not device_mods: if not device_mods:
debug("No modifications required") debug('No modifications required')
return return
device_paths = ", ".join([str(mod.device.device_info.path) for mod in device_mods]) device_paths = ', '.join([str(mod.device.device_info.path) for mod in device_mods])
if show_countdown: if show_countdown:
self._final_warning(device_paths) self._final_warning(device_paths)
@ -66,7 +66,7 @@ class FilesystemHandler:
if self._disk_config.lvm_config: if self._disk_config.lvm_config:
for mod in device_mods: for mod in device_mods:
if boot_part := mod.get_boot_partition(): if boot_part := mod.get_boot_partition():
debug(f"Formatting boot partition: {boot_part.dev_path}") debug(f'Formatting boot partition: {boot_part.dev_path}')
self._format_partitions( self._format_partitions(
[boot_part], [boot_part],
mod.device_path, mod.device_path,
@ -123,11 +123,11 @@ class FilesystemHandler:
def _validate_partitions(self, partitions: list[PartitionModification]) -> None: def _validate_partitions(self, partitions: list[PartitionModification]) -> None:
checks = { checks = {
# verify that all partitions have a path set (which implies that they have been created) # verify that all partitions have a path set (which implies that they have been created)
lambda x: x.dev_path is None: ValueError("When formatting, all partitions must have a path set"), lambda x: x.dev_path is None: ValueError('When formatting, all partitions must have a path set'),
# crypto luks is not a valid file system type # crypto luks is not a valid file system type
lambda x: x.fs_type is FilesystemType.Crypto_luks: ValueError("Crypto luks cannot be set as a filesystem type"), lambda x: x.fs_type is FilesystemType.Crypto_luks: ValueError('Crypto luks cannot be set as a filesystem type'),
# file system type must be set # file system type must be set
lambda x: x.fs_type is None: ValueError("File system type must be set for modification"), lambda x: x.fs_type is None: ValueError('File system type must be set for modification'),
} }
for check, exc in checks.items(): for check, exc in checks.items():
@ -136,7 +136,7 @@ class FilesystemHandler:
raise exc raise exc
def perform_lvm_operations(self) -> None: def perform_lvm_operations(self) -> None:
info("Setting up LVM config...") info('Setting up LVM config...')
if not self._disk_config.lvm_config: if not self._disk_config.lvm_config:
return return
@ -195,7 +195,7 @@ class FilesystemHandler:
vg_info = device_handler.lvm_group_info(vg.name) vg_info = device_handler.lvm_group_info(vg.name)
if not vg_info: if not vg_info:
raise ValueError("Unable to fetch VG info") raise ValueError('Unable to fetch VG info')
# the actual available LVM Group size will be smaller than the # the actual available LVM Group size will be smaller than the
# total PVs size due to reserved metadata storage etc. # total PVs size due to reserved metadata storage etc.
@ -213,11 +213,11 @@ class FilesystemHandler:
for lv in vg.volumes: for lv in vg.volumes:
offset = max_vol_offset if lv == max_vol else None offset = max_vol_offset if lv == max_vol else None
debug(f"vg: {vg.name}, vol: {lv.name}, offset: {offset}") debug(f'vg: {vg.name}, vol: {lv.name}, offset: {offset}')
device_handler.lvm_vol_create(vg.name, lv, offset) device_handler.lvm_vol_create(vg.name, lv, offset)
while True: while True:
debug("Fetching LVM volume info") debug('Fetching LVM volume info')
lv_info = device_handler.lvm_vol_info(lv.name) lv_info = device_handler.lvm_vol_info(lv.name)
if lv_info is not None: if lv_info is not None:
break break
@ -234,7 +234,7 @@ class FilesystemHandler:
for vol in lvm_config.get_all_volumes(): for vol in lvm_config.get_all_volumes():
if enc_vol := enc_vols.get(vol, None): if enc_vol := enc_vols.get(vol, None):
if not enc_vol.mapper_dev: if not enc_vol.mapper_dev:
raise ValueError("No mapper device defined") raise ValueError('No mapper device defined')
path = enc_vol.mapper_dev path = enc_vol.mapper_dev
else: else:
path = vol.safe_dev_path path = vol.safe_dev_path
@ -340,13 +340,13 @@ class FilesystemHandler:
def _final_warning(self, device_paths: str) -> bool: def _final_warning(self, device_paths: str) -> bool:
# Issue a final warning before we continue with something un-revertable. # Issue a final warning before we continue with something un-revertable.
# We mention the drive one last time, and count from 5 to 0. # We mention the drive one last time, and count from 5 to 0.
out = tr(" ! Formatting {} in ").format(device_paths) out = tr(' ! Formatting {} in ').format(device_paths)
Tui.print(out, row=0, endl="", clear_screen=True) Tui.print(out, row=0, endl='', clear_screen=True)
try: try:
countdown = "\n5...4...3...2...1\n" countdown = '\n5...4...3...2...1\n'
for c in countdown: for c in countdown:
Tui.print(c, row=0, endl="") Tui.print(c, row=0, endl='')
time.sleep(0.25) time.sleep(0.25)
except KeyboardInterrupt: except KeyboardInterrupt:
with Tui(): with Tui():

View File

@ -43,9 +43,9 @@ class FreeSpace:
Called for displaying data in table format Called for displaying data in table format
""" """
return { return {
"Start": self.start.format_size(Unit.sectors, self.start.sector_size, include_unit=False), 'Start': self.start.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
"End": self.end.format_size(Unit.sectors, self.start.sector_size, include_unit=False), 'End': self.end.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
"Size": self.length.format_highest(), 'Size': self.length.format_highest(),
} }
@ -67,7 +67,7 @@ class DiskSegment:
length=self.segment.length, length=self.segment.length,
) )
data = part_mod.table_data() data = part_mod.table_data()
data.update({"Status": "free", "Type": "", "FS type": ""}) data.update({'Status': 'free', 'Type': '', 'FS type': ''})
return data return data
@ -85,26 +85,26 @@ class PartitioningList(ListManager[DiskSegment]):
self._using_gpt = device_mod.using_gpt(partition_table) self._using_gpt = device_mod.using_gpt(partition_table)
self._actions = { self._actions = {
"suggest_partition_layout": tr("Suggest partition layout"), 'suggest_partition_layout': tr('Suggest partition layout'),
"remove_added_partitions": tr("Remove all newly added partitions"), 'remove_added_partitions': tr('Remove all newly added partitions'),
"assign_mountpoint": tr("Assign mountpoint"), 'assign_mountpoint': tr('Assign mountpoint'),
"mark_formatting": tr("Mark/Unmark to be formatted (wipes data)"), 'mark_formatting': tr('Mark/Unmark to be formatted (wipes data)'),
"mark_bootable": tr("Mark/Unmark as bootable"), 'mark_bootable': tr('Mark/Unmark as bootable'),
} }
if self._using_gpt: if self._using_gpt:
self._actions.update( self._actions.update(
{ {
"mark_esp": tr("Mark/Unmark as ESP"), 'mark_esp': tr('Mark/Unmark as ESP'),
"mark_xbootldr": tr("Mark/Unmark as XBOOTLDR"), 'mark_xbootldr': tr('Mark/Unmark as XBOOTLDR'),
} }
) )
self._actions.update( self._actions.update(
{ {
"set_filesystem": tr("Change filesystem"), 'set_filesystem': tr('Change filesystem'),
"btrfs_mark_compressed": tr("Mark/Unmark as compressed"), # btrfs only 'btrfs_mark_compressed': tr('Mark/Unmark as compressed'), # btrfs only
"btrfs_mark_nodatacow": tr("Mark/Unmark as nodatacow"), # btrfs only 'btrfs_mark_nodatacow': tr('Mark/Unmark as nodatacow'), # btrfs only
"btrfs_set_subvolumes": tr("Set subvolumes"), # btrfs only 'btrfs_set_subvolumes': tr('Set subvolumes'), # btrfs only
"delete_partition": tr("Delete partition"), 'delete_partition': tr('Delete partition'),
} }
) )
@ -119,9 +119,9 @@ class PartitioningList(ListManager[DiskSegment]):
else: else:
device_partitions = device_mod.partitions device_partitions = device_mod.partitions
prompt = tr("Partition management: {}").format(device.device_info.path) + "\n" prompt = tr('Partition management: {}').format(device.device_info.path) + '\n'
prompt += tr("Total length: {}").format(device.device_info.total_size.format_size(Unit.MiB)) prompt += tr('Total length: {}').format(device.device_info.total_size.format_size(Unit.MiB))
self._info = prompt + "\n" self._info = prompt + '\n'
display_actions = list(self._actions.values()) display_actions = list(self._actions.values())
super().__init__( super().__init__(
@ -132,7 +132,7 @@ class PartitioningList(ListManager[DiskSegment]):
) )
def wipe_str(self) -> str: def wipe_str(self) -> str:
return "{}: {}".format(tr("Wipe"), self._wipe) return '{}: {}'.format(tr('Wipe'), self._wipe)
def as_segments(self, device_partitions: list[PartitionModification]) -> list[DiskSegment]: def as_segments(self, device_partitions: list[PartitionModification]) -> list[DiskSegment]:
end = self._device.device_info.total_size end = self._device.device_info.total_size
@ -202,7 +202,7 @@ class PartitioningList(ListManager[DiskSegment]):
def _run_actions_on_entry(self, entry: DiskSegment) -> None: def _run_actions_on_entry(self, entry: DiskSegment) -> None:
# Do not create a menu when the segment is free space # Do not create a menu when the segment is free space
if isinstance(entry.segment, FreeSpace): if isinstance(entry.segment, FreeSpace):
self._data = self.handle_action("", entry, self._data) self._data = self.handle_action('', entry, self._data)
else: else:
super()._run_actions_on_entry(entry) super()._run_actions_on_entry(entry)
@ -210,18 +210,18 @@ class PartitioningList(ListManager[DiskSegment]):
def selected_action_display(self, selection: DiskSegment) -> str: def selected_action_display(self, selection: DiskSegment) -> str:
if isinstance(selection.segment, PartitionModification): if isinstance(selection.segment, PartitionModification):
if selection.segment.status == ModificationStatus.Create: if selection.segment.status == ModificationStatus.Create:
return tr("Partition - New") return tr('Partition - New')
elif selection.segment.is_delete() and selection.segment.dev_path: elif selection.segment.is_delete() and selection.segment.dev_path:
title = tr("Partition") + "\n\n" title = tr('Partition') + '\n\n'
title += "status: delete\n" title += 'status: delete\n'
title += f"device: {selection.segment.dev_path}\n" title += f'device: {selection.segment.dev_path}\n'
for part in self._device.partition_infos: for part in self._device.partition_infos:
if part.path == selection.segment.dev_path: if part.path == selection.segment.dev_path:
if part.partuuid: if part.partuuid:
title += f"partuuid: {part.partuuid}" title += f'partuuid: {part.partuuid}'
return title return title
return str(selection.segment.dev_path) return str(selection.segment.dev_path)
return "" return ''
@override @override
def filter_options(self, selection: DiskSegment, options: list[str]) -> list[str]: def filter_options(self, selection: DiskSegment, options: list[str]) -> list[str]:
@ -232,7 +232,7 @@ class PartitioningList(ListManager[DiskSegment]):
not_filter = list(self._actions.values()) not_filter = list(self._actions.values())
# only display formatting if the partition exists already # only display formatting if the partition exists already
elif not selection.segment.exists(): elif not selection.segment.exists():
not_filter += [self._actions["mark_formatting"]] not_filter += [self._actions['mark_formatting']]
else: else:
# only allow options if the existing partition # only allow options if the existing partition
# was marked as formatting, otherwise we run into issues where # was marked as formatting, otherwise we run into issues where
@ -240,29 +240,29 @@ class PartitioningList(ListManager[DiskSegment]):
# 2. Switch back to old filesystem -> should unmark wipe now, but # 2. Switch back to old filesystem -> should unmark wipe now, but
# how do we know it was the original one? # how do we know it was the original one?
not_filter += [ not_filter += [
self._actions["set_filesystem"], self._actions['set_filesystem'],
self._actions["mark_bootable"], self._actions['mark_bootable'],
] ]
if self._using_gpt: if self._using_gpt:
not_filter += [ not_filter += [
self._actions["mark_esp"], self._actions['mark_esp'],
self._actions["mark_xbootldr"], self._actions['mark_xbootldr'],
] ]
not_filter += [ not_filter += [
self._actions["btrfs_mark_compressed"], self._actions['btrfs_mark_compressed'],
self._actions["btrfs_mark_nodatacow"], self._actions['btrfs_mark_nodatacow'],
self._actions["btrfs_set_subvolumes"], self._actions['btrfs_set_subvolumes'],
] ]
# non btrfs partitions shouldn't get btrfs options # non btrfs partitions shouldn't get btrfs options
if selection.segment.fs_type != FilesystemType.Btrfs: if selection.segment.fs_type != FilesystemType.Btrfs:
not_filter += [ not_filter += [
self._actions["btrfs_mark_compressed"], self._actions['btrfs_mark_compressed'],
self._actions["btrfs_mark_nodatacow"], self._actions['btrfs_mark_nodatacow'],
self._actions["btrfs_set_subvolumes"], self._actions['btrfs_set_subvolumes'],
] ]
else: else:
not_filter += [self._actions["assign_mountpoint"]] not_filter += [self._actions['assign_mountpoint']]
return [o for o in options if o not in not_filter] return [o for o in options if o not in not_filter]
@ -276,21 +276,21 @@ class PartitioningList(ListManager[DiskSegment]):
if not entry: if not entry:
action_key = [k for k, v in self._actions.items() if v == action][0] action_key = [k for k, v in self._actions.items() if v == action][0]
match action_key: match action_key:
case "suggest_partition_layout": case 'suggest_partition_layout':
part_mods = self.get_part_mods(data) part_mods = self.get_part_mods(data)
device_mod = self._suggest_partition_layout(part_mods) device_mod = self._suggest_partition_layout(part_mods)
if device_mod and device_mod.partitions: if device_mod and device_mod.partitions:
data = self.as_segments(device_mod.partitions) data = self.as_segments(device_mod.partitions)
self._wipe = device_mod.wipe self._wipe = device_mod.wipe
self._prompt = self._info + self.wipe_str() self._prompt = self._info + self.wipe_str()
case "remove_added_partitions": case 'remove_added_partitions':
if self._reset_confirmation(): if self._reset_confirmation():
data = [s for s in data if isinstance(s.segment, PartitionModification) and s.segment.is_exists_or_modify()] data = [s for s in data if isinstance(s.segment, PartitionModification) and s.segment.is_exists_or_modify()]
elif isinstance(entry.segment, PartitionModification): elif isinstance(entry.segment, PartitionModification):
partition = entry.segment partition = entry.segment
action_key = [k for k, v in self._actions.items() if v == action][0] action_key = [k for k, v in self._actions.items() if v == action][0]
match action_key: match action_key:
case "assign_mountpoint": case 'assign_mountpoint':
new_mountpoint = self._prompt_mountpoint() new_mountpoint = self._prompt_mountpoint()
if not partition.is_swap(): if not partition.is_swap():
if partition.is_home(): if partition.is_home():
@ -306,22 +306,22 @@ class PartitioningList(ListManager[DiskSegment]):
if partition.is_home(): if partition.is_home():
partition.flags = [] partition.flags = []
partition.set_flag(PartitionFlag.LINUX_HOME) partition.set_flag(PartitionFlag.LINUX_HOME)
case "mark_formatting": case 'mark_formatting':
self._prompt_formatting(partition) self._prompt_formatting(partition)
case "mark_bootable": case 'mark_bootable':
if not partition.is_swap(): if not partition.is_swap():
partition.invert_flag(PartitionFlag.BOOT) partition.invert_flag(PartitionFlag.BOOT)
case "mark_esp": case 'mark_esp':
if not partition.is_root() and not partition.is_home() and not partition.is_swap(): if not partition.is_root() and not partition.is_home() and not partition.is_swap():
if PartitionFlag.XBOOTLDR in partition.flags: if PartitionFlag.XBOOTLDR in partition.flags:
partition.invert_flag(PartitionFlag.XBOOTLDR) partition.invert_flag(PartitionFlag.XBOOTLDR)
partition.invert_flag(PartitionFlag.ESP) partition.invert_flag(PartitionFlag.ESP)
case "mark_xbootldr": case 'mark_xbootldr':
if not partition.is_root() and not partition.is_home() and not partition.is_swap(): if not partition.is_root() and not partition.is_home() and not partition.is_swap():
if PartitionFlag.ESP in partition.flags: if PartitionFlag.ESP in partition.flags:
partition.invert_flag(PartitionFlag.ESP) partition.invert_flag(PartitionFlag.ESP)
partition.invert_flag(PartitionFlag.XBOOTLDR) partition.invert_flag(PartitionFlag.XBOOTLDR)
case "set_filesystem": case 'set_filesystem':
fs_type = self._prompt_partition_fs_type() fs_type = self._prompt_partition_fs_type()
if fs_type: if fs_type:
if partition.is_swap(): if partition.is_swap():
@ -334,13 +334,13 @@ class PartitioningList(ListManager[DiskSegment]):
# btrfs subvolumes will define mountpoints # btrfs subvolumes will define mountpoints
if fs_type == FilesystemType.Btrfs: if fs_type == FilesystemType.Btrfs:
partition.mountpoint = None partition.mountpoint = None
case "btrfs_mark_compressed": case 'btrfs_mark_compressed':
self._toggle_mount_option(partition, BtrfsMountOption.compress) self._toggle_mount_option(partition, BtrfsMountOption.compress)
case "btrfs_mark_nodatacow": case 'btrfs_mark_nodatacow':
self._toggle_mount_option(partition, BtrfsMountOption.nodatacow) self._toggle_mount_option(partition, BtrfsMountOption.nodatacow)
case "btrfs_set_subvolumes": case 'btrfs_set_subvolumes':
self._set_btrfs_subvolumes(partition) self._set_btrfs_subvolumes(partition)
case "delete_partition": case 'delete_partition':
data = self._delete_partition(partition, data) data = self._delete_partition(partition, data)
else: else:
part_mods = self.get_part_mods(data) part_mods = self.get_part_mods(data)
@ -396,7 +396,7 @@ class PartitioningList(ListManager[DiskSegment]):
# without asking the user which inner-filesystem they want to use. Since the flag 'encrypted' = True is already set, # without asking the user which inner-filesystem they want to use. Since the flag 'encrypted' = True is already set,
# it's safe to change the filesystem for this partition. # it's safe to change the filesystem for this partition.
if partition.fs_type == FilesystemType.Crypto_luks: if partition.fs_type == FilesystemType.Crypto_luks:
prompt = tr("This partition is currently encrypted, to format it a filesystem has to be specified") + "\n" prompt = tr('This partition is currently encrypted, to format it a filesystem has to be specified') + '\n'
fs_type = self._prompt_partition_fs_type(prompt) fs_type = self._prompt_partition_fs_type(prompt)
partition.fs_type = fs_type partition.fs_type = fs_type
@ -404,8 +404,8 @@ class PartitioningList(ListManager[DiskSegment]):
partition.mountpoint = None partition.mountpoint = None
def _prompt_mountpoint(self) -> Path: def _prompt_mountpoint(self) -> Path:
header = tr("Partition mount-points are relative to inside the installation, the boot would be /boot as an example.") + "\n" header = tr('Partition mount-points are relative to inside the installation, the boot would be /boot as an example.') + '\n'
prompt = tr("Mountpoint") prompt = tr('Mountpoint')
mountpoint = prompt_dir(prompt, header, validate=False, allow_skip=False) mountpoint = prompt_dir(prompt, header, validate=False, allow_skip=False)
assert mountpoint assert mountpoint
@ -421,7 +421,7 @@ class PartitioningList(ListManager[DiskSegment]):
group, group,
header=prompt, header=prompt,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Filesystem")), frame=FrameProperties.min(tr('Filesystem')),
allow_skip=False, allow_skip=False,
).run() ).run()
@ -429,7 +429,7 @@ class PartitioningList(ListManager[DiskSegment]):
case ResultType.Selection: case ResultType.Selection:
return result.get_value() return result.get_value()
case _: case _:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
def _validate_value( def _validate_value(
self, self,
@ -437,14 +437,14 @@ class PartitioningList(ListManager[DiskSegment]):
max_size: Size, max_size: Size,
text: str, text: str,
) -> Size | None: ) -> Size | None:
match = re.match(r"([0-9]+)([a-zA-Z|%]*)", text, re.I) match = re.match(r'([0-9]+)([a-zA-Z|%]*)', text, re.I)
if not match: if not match:
return None return None
str_value, unit = match.groups() str_value, unit = match.groups()
if unit == "%": if unit == '%':
value = int(max_size.value * (int(str_value) / 100)) value = int(max_size.value * (int(str_value) / 100))
unit = max_size.unit.name unit = max_size.unit.name
else: else:
@ -467,30 +467,30 @@ class PartitioningList(ListManager[DiskSegment]):
def validate(value: str) -> str | None: def validate(value: str) -> str | None:
size = self._validate_value(sector_size, max_size, value) size = self._validate_value(sector_size, max_size, value)
if not size: if not size:
return tr("Invalid size") return tr('Invalid size')
return None return None
device_info = self._device.device_info device_info = self._device.device_info
sector_size = device_info.sector_size sector_size = device_info.sector_size
text = tr("Selected free space segment on device {}:").format(device_info.path) + "\n\n" text = tr('Selected free space segment on device {}:').format(device_info.path) + '\n\n'
free_space_table = FormattedOutput.as_table([free_space]) free_space_table = FormattedOutput.as_table([free_space])
prompt = text + free_space_table + "\n" prompt = text + free_space_table + '\n'
max_sectors = free_space.length.format_size(Unit.sectors, sector_size) max_sectors = free_space.length.format_size(Unit.sectors, sector_size)
max_bytes = free_space.length.format_size(Unit.B) max_bytes = free_space.length.format_size(Unit.B)
prompt += tr("Size: {} / {}").format(max_sectors, max_bytes) + "\n\n" prompt += tr('Size: {} / {}').format(max_sectors, max_bytes) + '\n\n'
prompt += tr("All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB...") + "\n" prompt += tr('All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB...') + '\n'
prompt += tr("If no unit is provided, the value is interpreted as sectors") + "\n" prompt += tr('If no unit is provided, the value is interpreted as sectors') + '\n'
max_size = free_space.length max_size = free_space.length
title = tr("Size (default: {}): ").format(max_size.format_highest()) title = tr('Size (default: {}): ').format(max_size.format_highest())
result = EditMenu( result = EditMenu(
title, title,
header=f"{prompt}\b", header=f'{prompt}\b',
allow_skip=True, allow_skip=True,
validator=validate, validator=validate,
).input() ).input()
@ -529,7 +529,7 @@ class PartitioningList(ListManager[DiskSegment]):
mountpoint=mountpoint, mountpoint=mountpoint,
) )
if partition.mountpoint == Path("/boot"): if partition.mountpoint == Path('/boot'):
partition.set_flag(PartitionFlag.BOOT) partition.set_flag(PartitionFlag.BOOT)
if self._using_gpt: if self._using_gpt:
partition.set_flag(PartitionFlag.ESP) partition.set_flag(PartitionFlag.ESP)
@ -541,7 +541,7 @@ class PartitioningList(ListManager[DiskSegment]):
return partition return partition
def _reset_confirmation(self) -> bool: def _reset_confirmation(self) -> bool:
prompt = tr("This will remove all newly added partitions, continue?") + "\n" prompt = tr('This will remove all newly added partitions, continue?') + '\n'
result = SelectMenu[bool]( result = SelectMenu[bool](
MenuItemGroup.yes_no(), MenuItemGroup.yes_no(),

View File

@ -18,9 +18,9 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
prompt: str | None = None, prompt: str | None = None,
): ):
self._actions = [ self._actions = [
tr("Add subvolume"), tr('Add subvolume'),
tr("Edit subvolume"), tr('Edit subvolume'),
tr("Delete subvolume"), tr('Delete subvolume'),
] ]
super().__init__( super().__init__(
@ -36,7 +36,7 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
def _add_subvolume(self, preset: SubvolumeModification | None = None) -> SubvolumeModification | None: def _add_subvolume(self, preset: SubvolumeModification | None = None) -> SubvolumeModification | None:
result = EditMenu( result = EditMenu(
tr("Subvolume name"), tr('Subvolume name'),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
allow_skip=True, allow_skip=True,
default_text=str(preset.name) if preset else None, default_text=str(preset.name) if preset else None,
@ -48,14 +48,14 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
case ResultType.Selection: case ResultType.Selection:
name = result.text() name = result.text()
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
case _: case _:
assert_never(result.type_) assert_never(result.type_)
header = f"{tr('Subvolume name')}: {name}\n" header = f'{tr("Subvolume name")}: {name}\n'
path = prompt_dir( path = prompt_dir(
tr("Subvolume mountpoint"), tr('Subvolume mountpoint'),
header=header, header=header,
allow_skip=True, allow_skip=True,
validate=False, validate=False,

View File

@ -17,13 +17,13 @@ def _fetch_lsblk_info(
reverse: bool = False, reverse: bool = False,
full_dev_path: bool = False, full_dev_path: bool = False,
) -> LsblkOutput: ) -> LsblkOutput:
cmd = ["lsblk", "--json", "--bytes", "--output", ",".join(LsblkInfo.fields())] cmd = ['lsblk', '--json', '--bytes', '--output', ','.join(LsblkInfo.fields())]
if reverse: if reverse:
cmd.append("--inverse") cmd.append('--inverse')
if full_dev_path: if full_dev_path:
cmd.append("--paths") cmd.append('--paths')
if dev_path: if dev_path:
cmd.append(str(dev_path)) cmd.append(str(dev_path))
@ -33,7 +33,7 @@ def _fetch_lsblk_info(
except SysCallError as err: except SysCallError as err:
# Get the output minus the message/info from lsblk if it returns a non-zero exit code. # Get the output minus the message/info from lsblk if it returns a non-zero exit code.
if err.worker_log: if err.worker_log:
debug(f"Error calling lsblk: {err.worker_log.decode()}") debug(f'Error calling lsblk: {err.worker_log.decode()}')
if dev_path: if dev_path:
raise DiskError(f'Failed to read disk "{dev_path}" with lsblk') raise DiskError(f'Failed to read disk "{dev_path}" with lsblk')
@ -104,8 +104,8 @@ def disk_layouts() -> str:
try: try:
lsblk_output = get_lsblk_output() lsblk_output = get_lsblk_output()
except SysCallError as err: except SysCallError as err:
warn(f"Could not return disk layouts: {err}") warn(f'Could not return disk layouts: {err}')
return "" return ''
return lsblk_output.model_dump_json(indent=4) return lsblk_output.model_dump_json(indent=4)
@ -116,13 +116,13 @@ def umount(mountpoint: Path, recursive: bool = False) -> None:
if not lsblk_info.mountpoints: if not lsblk_info.mountpoints:
return return
debug(f"Partition {mountpoint} is currently mounted at: {[str(m) for m in lsblk_info.mountpoints]}") debug(f'Partition {mountpoint} is currently mounted at: {[str(m) for m in lsblk_info.mountpoints]}')
cmd = ["umount"] cmd = ['umount']
if recursive: if recursive:
cmd.append("-R") cmd.append('-R')
for path in lsblk_info.mountpoints: for path in lsblk_info.mountpoints:
debug(f"Unmounting mountpoint: {path}") debug(f'Unmounting mountpoint: {path}')
SysCommand(cmd + [str(path)]) SysCommand(cmd + [str(path)])

View File

@ -11,7 +11,7 @@ class UnknownFilesystemFormat(Exception):
class SysCallError(Exception): class SysCallError(Exception):
def __init__(self, message: str, exit_code: int | None = None, worker_log: bytes = b"") -> None: def __init__(self, message: str, exit_code: int | None = None, worker_log: bytes = b'') -> None:
super().__init__(message) super().__init__(message)
self.message = message self.message = message
self.exit_code = exit_code self.exit_code = exit_code

View File

@ -23,27 +23,27 @@ from .output import debug, error
from .storage import storage from .storage import storage
# https://stackoverflow.com/a/43627833/929999 # https://stackoverflow.com/a/43627833/929999
_VT100_ESCAPE_REGEX = r"\x1B\[[?0-9;]*[a-zA-Z]" _VT100_ESCAPE_REGEX = r'\x1B\[[?0-9;]*[a-zA-Z]'
_VT100_ESCAPE_REGEX_BYTES = _VT100_ESCAPE_REGEX.encode() _VT100_ESCAPE_REGEX_BYTES = _VT100_ESCAPE_REGEX.encode()
def generate_password(length: int = 64) -> str: def generate_password(length: int = 64) -> str:
haystack = string.printable # digits, ascii_letters, punctuation (!"#$[] etc) and whitespace haystack = string.printable # digits, ascii_letters, punctuation (!"#$[] etc) and whitespace
return "".join(secrets.choice(haystack) for _ in range(length)) return ''.join(secrets.choice(haystack) for _ in range(length))
def locate_binary(name: str) -> str: def locate_binary(name: str) -> str:
if path := which(name): if path := which(name):
return path return path
raise RequirementError(f"Binary {name} does not exist.") raise RequirementError(f'Binary {name} does not exist.')
def clear_vt100_escape_codes(data: bytes) -> bytes: def clear_vt100_escape_codes(data: bytes) -> bytes:
return re.sub(_VT100_ESCAPE_REGEX_BYTES, b"", data) return re.sub(_VT100_ESCAPE_REGEX_BYTES, b'', data)
def clear_vt100_escape_codes_from_str(data: str) -> str: def clear_vt100_escape_codes_from_str(data: str) -> str:
return re.sub(_VT100_ESCAPE_REGEX, "", data) return re.sub(_VT100_ESCAPE_REGEX, '', data)
def jsonify(obj: object, safe: bool = True) -> object: def jsonify(obj: object, safe: bool = True) -> object:
@ -57,11 +57,11 @@ def jsonify(obj: object, safe: bool = True) -> object:
return { return {
key: jsonify(value, safe) key: jsonify(value, safe)
for key, value in obj.items() for key, value in obj.items()
if isinstance(key, compatible_types) and not (isinstance(key, str) and key.startswith("!") and safe) if isinstance(key, compatible_types) and not (isinstance(key, str) and key.startswith('!') and safe)
} }
if isinstance(obj, Enum): if isinstance(obj, Enum):
return obj.value return obj.value
if hasattr(obj, "json"): if hasattr(obj, 'json'):
# json() is a friendly name for json-helper, it should return # json() is a friendly name for json-helper, it should return
# a dictionary representation of the object so that it can be # a dictionary representation of the object so that it can be
# processed by the json library. # processed by the json library.
@ -72,7 +72,7 @@ def jsonify(obj: object, safe: bool = True) -> object:
return [jsonify(item, safe) for item in obj] return [jsonify(item, safe) for item in obj]
if isinstance(obj, Path): if isinstance(obj, Path):
return str(obj) return str(obj)
if hasattr(obj, "__dict__"): if hasattr(obj, '__dict__'):
return vars(obj) return vars(obj)
return obj return obj
@ -104,26 +104,26 @@ class SysCommandWorker:
cmd: str | list[str], cmd: str | list[str],
peek_output: bool | None = False, peek_output: bool | None = False,
environment_vars: dict[str, str] | None = None, environment_vars: dict[str, str] | None = None,
working_directory: str | None = "./", working_directory: str | None = './',
remove_vt100_escape_codes_from_lines: bool = True, remove_vt100_escape_codes_from_lines: bool = True,
): ):
if isinstance(cmd, str): if isinstance(cmd, str):
cmd = shlex.split(cmd) cmd = shlex.split(cmd)
if cmd and not cmd[0].startswith(("/", "./")): # Path() does not work well if cmd and not cmd[0].startswith(('/', './')): # Path() does not work well
cmd[0] = locate_binary(cmd[0]) cmd[0] = locate_binary(cmd[0])
self.cmd = cmd self.cmd = cmd
self.peek_output = peek_output self.peek_output = peek_output
# define the standard locale for command outputs. For now the C ascii one. Can be overridden # define the standard locale for command outputs. For now the C ascii one. Can be overridden
self.environment_vars = {"LC_ALL": "C"} self.environment_vars = {'LC_ALL': 'C'}
if environment_vars: if environment_vars:
self.environment_vars.update(environment_vars) self.environment_vars.update(environment_vars)
self.working_directory = working_directory self.working_directory = working_directory
self.exit_code: int | None = None self.exit_code: int | None = None
self._trace_log = b"" self._trace_log = b''
self._trace_log_pos = 0 self._trace_log_pos = 0
self.poll_object = epoll() self.poll_object = epoll()
self.child_fd: int | None = None self.child_fd: int | None = None
@ -146,13 +146,13 @@ class SysCommandWorker:
return False return False
def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]: def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]:
last_line = self._trace_log.rfind(b"\n") last_line = self._trace_log.rfind(b'\n')
lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines()) lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines())
for line in lines: for line in lines:
if self.remove_vt100_escape_codes_from_lines: if self.remove_vt100_escape_codes_from_lines:
line = clear_vt100_escape_codes(line) line = clear_vt100_escape_codes(line)
yield line + b"\n" yield line + b'\n'
self._trace_log_pos = last_line self._trace_log_pos = last_line
@ -164,11 +164,11 @@ class SysCommandWorker:
@override @override
def __str__(self) -> str: def __str__(self) -> str:
try: try:
return self._trace_log.decode("utf-8") return self._trace_log.decode('utf-8')
except UnicodeDecodeError: except UnicodeDecodeError:
return str(self._trace_log) return str(self._trace_log)
def __enter__(self) -> "SysCommandWorker": def __enter__(self) -> 'SysCommandWorker':
return self return self
def __exit__(self, *args: str) -> None: def __exit__(self, *args: str) -> None:
@ -184,7 +184,7 @@ class SysCommandWorker:
if self.peek_output: if self.peek_output:
# To make sure any peaked output didn't leave us hanging # To make sure any peaked output didn't leave us hanging
# on the same line we were on. # on the same line we were on.
sys.stdout.write("\n") sys.stdout.write('\n')
sys.stdout.flush() sys.stdout.flush()
if len(args) >= 2 and args[1]: if len(args) >= 2 and args[1]:
@ -192,7 +192,7 @@ class SysCommandWorker:
if self.exit_code != 0: if self.exit_code != 0:
raise SysCallError( raise SysCallError(
f"{self.cmd} exited with abnormal exit code [{self.exit_code}]: {str(self)[-500:]}", f'{self.cmd} exited with abnormal exit code [{self.exit_code}]: {str(self)[-500:]}',
self.exit_code, self.exit_code,
worker_log=self._trace_log, worker_log=self._trace_log,
) )
@ -211,7 +211,7 @@ class SysCommandWorker:
self.make_sure_we_are_executing() self.make_sure_we_are_executing()
if self.child_fd: if self.child_fd:
return os.write(self.child_fd, data + (b"\n" if line_ending else b"")) return os.write(self.child_fd, data + (b'\n' if line_ending else b''))
return 0 return 0
@ -233,17 +233,17 @@ class SysCommandWorker:
if self.peek_output: if self.peek_output:
if isinstance(output, bytes): if isinstance(output, bytes):
try: try:
output = output.decode("UTF-8") output = output.decode('UTF-8')
except UnicodeDecodeError: except UnicodeDecodeError:
return False return False
peak_logfile = Path(f"{storage['LOG_PATH']}/cmd_output.txt") peak_logfile = Path(f'{storage["LOG_PATH"]}/cmd_output.txt')
change_perm = False change_perm = False
if peak_logfile.exists() is False: if peak_logfile.exists() is False:
change_perm = True change_perm = True
with peak_logfile.open("a") as peek_output_log: with peak_logfile.open('a') as peek_output_log:
peek_output_log.write(str(output)) peek_output_log.write(str(output))
if change_perm: if change_perm:
@ -301,7 +301,7 @@ class SysCommandWorker:
try: try:
os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars}) os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars})
except FileNotFoundError: except FileNotFoundError:
error(f"{self.cmd[0]} does not exist.") error(f'{self.cmd[0]} does not exist.')
self.exit_code = 1 self.exit_code = 1
return False return False
else: else:
@ -313,7 +313,7 @@ class SysCommandWorker:
return True return True
def decode(self, encoding: str = "UTF-8") -> str: def decode(self, encoding: str = 'UTF-8') -> str:
return self._trace_log.decode(encoding) return self._trace_log.decode(encoding)
@ -323,7 +323,7 @@ class SysCommand:
cmd: str | list[str], cmd: str | list[str],
peek_output: bool | None = False, peek_output: bool | None = False,
environment_vars: dict[str, str] | None = None, environment_vars: dict[str, str] | None = None,
working_directory: str | None = "./", working_directory: str | None = './',
remove_vt100_escape_codes_from_lines: bool = True, remove_vt100_escape_codes_from_lines: bool = True,
): ):
self.cmd = cmd self.cmd = cmd
@ -351,7 +351,7 @@ class SysCommand:
def __getitem__(self, key: slice) -> bytes | None: def __getitem__(self, key: slice) -> bytes | None:
if not self.session: if not self.session:
raise KeyError("SysCommand() does not have an active session.") raise KeyError('SysCommand() does not have an active session.')
elif type(key) is slice: elif type(key) is slice:
start = key.start or 0 start = key.start or 0
end = key.stop or len(self.session._trace_log) end = key.stop or len(self.session._trace_log)
@ -362,7 +362,7 @@ class SysCommand:
@override @override
def __repr__(self, *args: list[Any], **kwargs: dict[str, Any]) -> str: def __repr__(self, *args: list[Any], **kwargs: dict[str, Any]) -> str:
return self.decode("UTF-8", errors="backslashreplace") or "" return self.decode('UTF-8', errors='backslashreplace') or ''
def create_session(self) -> bool: def create_session(self) -> bool:
""" """
@ -386,14 +386,14 @@ class SysCommand:
self.session.poll() self.session.poll()
if self.peek_output: if self.peek_output:
sys.stdout.write("\n") sys.stdout.write('\n')
sys.stdout.flush() sys.stdout.flush()
return True return True
def decode(self, encoding: str = "utf-8", errors: str = "backslashreplace", strip: bool = True) -> str: def decode(self, encoding: str = 'utf-8', errors: str = 'backslashreplace', strip: bool = True) -> str:
if not self.session: if not self.session:
raise ValueError("No session available to decode") raise ValueError('No session available to decode')
val = self.session._trace_log.decode(encoding, errors=errors) val = self.session._trace_log.decode(encoding, errors=errors)
@ -403,10 +403,10 @@ class SysCommand:
def output(self, remove_cr: bool = True) -> bytes: def output(self, remove_cr: bool = True) -> bytes:
if not self.session: if not self.session:
raise ValueError("No session available") raise ValueError('No session available')
if remove_cr: if remove_cr:
return self.session._trace_log.replace(b"\r\n", b"\n") return self.session._trace_log.replace(b'\r\n', b'\n')
return self.session._trace_log return self.session._trace_log
@ -425,15 +425,15 @@ class SysCommand:
def _log_cmd(cmd: list[str]) -> None: def _log_cmd(cmd: list[str]) -> None:
history_logfile = Path(f"{storage['LOG_PATH']}/cmd_history.txt") history_logfile = Path(f'{storage["LOG_PATH"]}/cmd_history.txt')
change_perm = False change_perm = False
if history_logfile.exists() is False: if history_logfile.exists() is False:
change_perm = True change_perm = True
try: try:
with history_logfile.open("a") as cmd_log: with history_logfile.open('a') as cmd_log:
cmd_log.write(f"{time.time()} {cmd}\n") cmd_log.write(f'{time.time()} {cmd}\n')
if change_perm: if change_perm:
history_logfile.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP) history_logfile.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
@ -459,6 +459,6 @@ def run(
def _pid_exists(pid: int) -> bool: def _pid_exists(pid: int) -> bool:
try: try:
return any(subprocess.check_output(["ps", "--no-headers", "-o", "pid", "-p", str(pid)]).strip()) return any(subprocess.check_output(['ps', '--no-headers', '-o', 'pid', '-p', str(pid)]).strip())
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
return False return False

View File

@ -55,151 +55,151 @@ class GlobalMenu(AbstractMenu[None]):
def _get_menu_options(self) -> list[MenuItem]: def _get_menu_options(self) -> list[MenuItem]:
return [ return [
MenuItem( MenuItem(
text=tr("Archinstall language"), text=tr('Archinstall language'),
action=self._select_archinstall_language, action=self._select_archinstall_language,
display_action=lambda x: x.display_name if x else "", display_action=lambda x: x.display_name if x else '',
key="archinstall_language", key='archinstall_language',
), ),
MenuItem( MenuItem(
text=tr("Locales"), text=tr('Locales'),
action=self._locale_selection, action=self._locale_selection,
preview_action=self._prev_locale, preview_action=self._prev_locale,
key="locale_config", key='locale_config',
), ),
MenuItem( MenuItem(
text=tr("Mirrors and repositories"), text=tr('Mirrors and repositories'),
action=self._mirror_configuration, action=self._mirror_configuration,
preview_action=self._prev_mirror_config, preview_action=self._prev_mirror_config,
key="mirror_config", key='mirror_config',
), ),
MenuItem( MenuItem(
text=tr("Disk configuration"), text=tr('Disk configuration'),
action=self._select_disk_config, action=self._select_disk_config,
preview_action=self._prev_disk_config, preview_action=self._prev_disk_config,
mandatory=True, mandatory=True,
key="disk_config", key='disk_config',
), ),
MenuItem( MenuItem(
text=tr("Disk encryption"), text=tr('Disk encryption'),
action=self._disk_encryption, action=self._disk_encryption,
preview_action=self._prev_disk_encryption, preview_action=self._prev_disk_encryption,
dependencies=["disk_config"], dependencies=['disk_config'],
key="disk_encryption", key='disk_encryption',
), ),
MenuItem( MenuItem(
text=tr("Swap"), text=tr('Swap'),
value=True, value=True,
action=ask_for_swap, action=ask_for_swap,
preview_action=self._prev_swap, preview_action=self._prev_swap,
key="swap", key='swap',
), ),
MenuItem( MenuItem(
text=tr("Bootloader"), text=tr('Bootloader'),
value=Bootloader.get_default(), value=Bootloader.get_default(),
action=self._select_bootloader, action=self._select_bootloader,
preview_action=self._prev_bootloader, preview_action=self._prev_bootloader,
mandatory=True, mandatory=True,
key="bootloader", key='bootloader',
), ),
MenuItem( MenuItem(
text=tr("Unified kernel images"), text=tr('Unified kernel images'),
value=False, value=False,
enabled=SysInfo.has_uefi(), enabled=SysInfo.has_uefi(),
action=ask_for_uki, action=ask_for_uki,
preview_action=self._prev_uki, preview_action=self._prev_uki,
key="uki", key='uki',
), ),
MenuItem( MenuItem(
text=tr("Hostname"), text=tr('Hostname'),
value="archlinux", value='archlinux',
action=ask_hostname, action=ask_hostname,
preview_action=self._prev_hostname, preview_action=self._prev_hostname,
key="hostname", key='hostname',
), ),
MenuItem( MenuItem(
text=tr("Root password"), text=tr('Root password'),
action=self._set_root_password, action=self._set_root_password,
preview_action=self._prev_root_pwd, preview_action=self._prev_root_pwd,
key="root_enc_password", key='root_enc_password',
), ),
MenuItem( MenuItem(
text=tr("User account"), text=tr('User account'),
action=self._create_user_account, action=self._create_user_account,
preview_action=self._prev_users, preview_action=self._prev_users,
key="users", key='users',
), ),
MenuItem( MenuItem(
text=tr("Profile"), text=tr('Profile'),
action=self._select_profile, action=self._select_profile,
preview_action=self._prev_profile, preview_action=self._prev_profile,
key="profile_config", key='profile_config',
), ),
MenuItem( MenuItem(
text=tr("Audio"), text=tr('Audio'),
action=ask_for_audio_selection, action=ask_for_audio_selection,
preview_action=self._prev_audio, preview_action=self._prev_audio,
key="audio_config", key='audio_config',
), ),
MenuItem( MenuItem(
text=tr("Kernels"), text=tr('Kernels'),
value=["linux"], value=['linux'],
action=select_kernel, action=select_kernel,
preview_action=self._prev_kernel, preview_action=self._prev_kernel,
mandatory=True, mandatory=True,
key="kernels", key='kernels',
), ),
MenuItem( MenuItem(
text=tr("Network configuration"), text=tr('Network configuration'),
action=ask_to_configure_network, action=ask_to_configure_network,
value={}, value={},
preview_action=self._prev_network_config, preview_action=self._prev_network_config,
key="network_config", key='network_config',
), ),
MenuItem( MenuItem(
text=tr("Parallel Downloads"), text=tr('Parallel Downloads'),
action=add_number_of_parallel_downloads, action=add_number_of_parallel_downloads,
value=0, value=0,
preview_action=self._prev_parallel_dw, preview_action=self._prev_parallel_dw,
key="parallel_downloads", key='parallel_downloads',
), ),
MenuItem( MenuItem(
text=tr("Additional packages"), text=tr('Additional packages'),
action=self._select_additional_packages, action=self._select_additional_packages,
value=[], value=[],
preview_action=self._prev_additional_pkgs, preview_action=self._prev_additional_pkgs,
key="packages", key='packages',
), ),
MenuItem( MenuItem(
text=tr("Timezone"), text=tr('Timezone'),
action=ask_for_a_timezone, action=ask_for_a_timezone,
value="UTC", value='UTC',
preview_action=self._prev_tz, preview_action=self._prev_tz,
key="timezone", key='timezone',
), ),
MenuItem( MenuItem(
text=tr("Automatic time sync (NTP)"), text=tr('Automatic time sync (NTP)'),
action=ask_ntp, action=ask_ntp,
value=True, value=True,
preview_action=self._prev_ntp, preview_action=self._prev_ntp,
key="ntp", key='ntp',
), ),
MenuItem( MenuItem(
text="", text='',
), ),
MenuItem( MenuItem(
text=tr("Save configuration"), text=tr('Save configuration'),
action=lambda x: self._safe_config(), action=lambda x: self._safe_config(),
key=f"{CONFIG_KEY}_save", key=f'{CONFIG_KEY}_save',
), ),
MenuItem( MenuItem(
text=tr("Install"), text=tr('Install'),
preview_action=self._prev_install_invalid_config, preview_action=self._prev_install_invalid_config,
key=f"{CONFIG_KEY}_install", key=f'{CONFIG_KEY}_install',
), ),
MenuItem( MenuItem(
text=tr("Abort"), text=tr('Abort'),
action=lambda x: exit(1), action=lambda x: exit(1),
key=f"{CONFIG_KEY}_abort", key=f'{CONFIG_KEY}_abort',
), ),
] ]
@ -218,7 +218,7 @@ class GlobalMenu(AbstractMenu[None]):
return item.has_value() return item.has_value()
def has_superuser() -> bool: def has_superuser() -> bool:
item = self._item_group.find_by_key("users") item = self._item_group.find_by_key('users')
if item.has_value(): if item.has_value():
users = item.value users = item.value
@ -229,10 +229,10 @@ class GlobalMenu(AbstractMenu[None]):
missing = set() missing = set()
for item in self._item_group.items: for item in self._item_group.items:
if item.key in ["root_enc_password", "users"]: if item.key in ['root_enc_password', 'users']:
if not check("root_enc_password") and not has_superuser(): if not check('root_enc_password') and not has_superuser():
missing.add( missing.add(
tr("Either root-password or at least 1 user with sudo privileges must be specified"), tr('Either root-password or at least 1 user with sudo privileges must be specified'),
) )
elif item.mandatory: elif item.mandatory:
if not check(item.key): if not check(item.key):
@ -271,11 +271,11 @@ class GlobalMenu(AbstractMenu[None]):
self._item_group.find_by_key(o.key).text = o.text self._item_group.find_by_key(o.key).text = o.text
def _disk_encryption(self, preset: DiskEncryption | None) -> DiskEncryption | None: def _disk_encryption(self, preset: DiskEncryption | None) -> DiskEncryption | None:
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key("disk_config").value disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
if not disk_config: if not disk_config:
# this should not happen as the encryption menu has the disk_config as dependency # this should not happen as the encryption menu has the disk_config as dependency
raise ValueError("No disk layout specified") raise ValueError('No disk layout specified')
if not DiskEncryption.validate_enc(disk_config): if not DiskEncryption.validate_enc(disk_config):
return None return None
@ -300,26 +300,26 @@ class GlobalMenu(AbstractMenu[None]):
if network_config.type == NicType.MANUAL: if network_config.type == NicType.MANUAL:
output = FormattedOutput.as_table(network_config.nics) output = FormattedOutput.as_table(network_config.nics)
else: else:
output = f"{tr('Network configuration')}:\n{network_config.type.display_msg()}" output = f'{tr("Network configuration")}:\n{network_config.type.display_msg()}'
return output return output
return None return None
def _prev_additional_pkgs(self, item: MenuItem) -> str | None: def _prev_additional_pkgs(self, item: MenuItem) -> str | None:
if item.value: if item.value:
output = "\n".join(sorted(item.value)) output = '\n'.join(sorted(item.value))
return output return output
return None return None
def _prev_tz(self, item: MenuItem) -> str | None: def _prev_tz(self, item: MenuItem) -> str | None:
if item.value: if item.value:
return f"{tr('Timezone')}: {item.value}" return f'{tr("Timezone")}: {item.value}'
return None return None
def _prev_ntp(self, item: MenuItem) -> str | None: def _prev_ntp(self, item: MenuItem) -> str | None:
if item.value is not None: if item.value is not None:
output = f"{tr('NTP')}: " output = f'{tr("NTP")}: '
output += tr("Enabled") if item.value else tr("Disabled") output += tr('Enabled') if item.value else tr('Disabled')
return output return output
return None return None
@ -327,13 +327,13 @@ class GlobalMenu(AbstractMenu[None]):
disk_layout_conf: DiskLayoutConfiguration | None = item.value disk_layout_conf: DiskLayoutConfiguration | None = item.value
if disk_layout_conf: if disk_layout_conf:
output = tr("Configuration type: {}").format(disk_layout_conf.config_type.display_msg()) + "\n" output = tr('Configuration type: {}').format(disk_layout_conf.config_type.display_msg()) + '\n'
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount: if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
output += tr("Mountpoint") + ": " + str(disk_layout_conf.mountpoint) output += tr('Mountpoint') + ': ' + str(disk_layout_conf.mountpoint)
if disk_layout_conf.lvm_config: if disk_layout_conf.lvm_config:
output += "{}: {}".format(tr("LVM configuration type"), disk_layout_conf.lvm_config.config_type.display_msg()) output += '{}: {}'.format(tr('LVM configuration type'), disk_layout_conf.lvm_config.config_type.display_msg())
return output return output
@ -341,72 +341,72 @@ class GlobalMenu(AbstractMenu[None]):
def _prev_swap(self, item: MenuItem) -> str | None: def _prev_swap(self, item: MenuItem) -> str | None:
if item.value is not None: if item.value is not None:
output = f"{tr('Swap on zram')}: " output = f'{tr("Swap on zram")}: '
output += tr("Enabled") if item.value else tr("Disabled") output += tr('Enabled') if item.value else tr('Disabled')
return output return output
return None return None
def _prev_uki(self, item: MenuItem) -> str | None: def _prev_uki(self, item: MenuItem) -> str | None:
if item.value is not None: if item.value is not None:
output = f"{tr('Unified kernel images')}: " output = f'{tr("Unified kernel images")}: '
output += tr("Enabled") if item.value else tr("Disabled") output += tr('Enabled') if item.value else tr('Disabled')
return output return output
return None return None
def _prev_hostname(self, item: MenuItem) -> str | None: def _prev_hostname(self, item: MenuItem) -> str | None:
if item.value is not None: if item.value is not None:
return f"{tr('Hostname')}: {item.value}" return f'{tr("Hostname")}: {item.value}'
return None return None
def _prev_root_pwd(self, item: MenuItem) -> str | None: def _prev_root_pwd(self, item: MenuItem) -> str | None:
if item.value is not None: if item.value is not None:
password: Password = item.value password: Password = item.value
return f"{tr('Root password')}: {password.hidden()}" return f'{tr("Root password")}: {password.hidden()}'
return None return None
def _prev_audio(self, item: MenuItem) -> str | None: def _prev_audio(self, item: MenuItem) -> str | None:
if item.value is not None: if item.value is not None:
config: AudioConfiguration = item.value config: AudioConfiguration = item.value
return f"{tr('Audio')}: {config.audio.value}" return f'{tr("Audio")}: {config.audio.value}'
return None return None
def _prev_parallel_dw(self, item: MenuItem) -> str | None: def _prev_parallel_dw(self, item: MenuItem) -> str | None:
if item.value is not None: if item.value is not None:
return f"{tr('Parallel Downloads')}: {item.value}" return f'{tr("Parallel Downloads")}: {item.value}'
return None return None
def _prev_kernel(self, item: MenuItem) -> str | None: def _prev_kernel(self, item: MenuItem) -> str | None:
if item.value: if item.value:
kernel = ", ".join(item.value) kernel = ', '.join(item.value)
return f"{tr('Kernel')}: {kernel}" return f'{tr("Kernel")}: {kernel}'
return None return None
def _prev_bootloader(self, item: MenuItem) -> str | None: def _prev_bootloader(self, item: MenuItem) -> str | None:
if item.value is not None: if item.value is not None:
return f"{tr('Bootloader')}: {item.value.value}" return f'{tr("Bootloader")}: {item.value.value}'
return None return None
def _prev_disk_encryption(self, item: MenuItem) -> str | None: def _prev_disk_encryption(self, item: MenuItem) -> str | None:
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key("disk_config").value disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
enc_config: DiskEncryption | None = item.value enc_config: DiskEncryption | None = item.value
if disk_config and not DiskEncryption.validate_enc(disk_config): if disk_config and not DiskEncryption.validate_enc(disk_config):
return tr("LVM disk encryption with more than 2 partitions is currently not supported") return tr('LVM disk encryption with more than 2 partitions is currently not supported')
if enc_config: if enc_config:
enc_type = EncryptionType.type_to_text(enc_config.encryption_type) enc_type = EncryptionType.type_to_text(enc_config.encryption_type)
output = tr("Encryption type") + f": {enc_type}\n" output = tr('Encryption type') + f': {enc_type}\n'
if enc_config.encryption_password: if enc_config.encryption_password:
output += tr("Password") + f": {enc_config.encryption_password.hidden()}\n" output += tr('Password') + f': {enc_config.encryption_password.hidden()}\n'
if enc_config.partitions: if enc_config.partitions:
output += f"Partitions: {len(enc_config.partitions)} selected\n" output += f'Partitions: {len(enc_config.partitions)} selected\n'
elif enc_config.lvm_volumes: elif enc_config.lvm_volumes:
output += f"LVM volumes: {len(enc_config.lvm_volumes)} selected\n" output += f'LVM volumes: {len(enc_config.lvm_volumes)} selected\n'
if enc_config.hsm_device: if enc_config.hsm_device:
output += f"HSM: {enc_config.hsm_device.manufacturer}" output += f'HSM: {enc_config.hsm_device.manufacturer}'
return output return output
@ -423,12 +423,12 @@ class GlobalMenu(AbstractMenu[None]):
XXX: The caller is responsible for wrapping the string with the translation XXX: The caller is responsible for wrapping the string with the translation
shim if necessary. shim if necessary.
""" """
bootloader = self._item_group.find_by_key("bootloader").value bootloader = self._item_group.find_by_key('bootloader').value
root_partition: PartitionModification | None = None root_partition: PartitionModification | None = None
boot_partition: PartitionModification | None = None boot_partition: PartitionModification | None = None
efi_partition: PartitionModification | None = None efi_partition: PartitionModification | None = None
if disk_config := self._item_group.find_by_key("disk_config").value: if disk_config := self._item_group.find_by_key('disk_config').value:
for layout in disk_config.device_modifications: for layout in disk_config.device_modifications:
if root_partition := layout.get_root_partition(): if root_partition := layout.get_root_partition():
break break
@ -440,36 +440,36 @@ class GlobalMenu(AbstractMenu[None]):
if efi_partition := layout.get_efi_partition(): if efi_partition := layout.get_efi_partition():
break break
else: else:
return "No disk layout selected" return 'No disk layout selected'
if root_partition is None: if root_partition is None:
return "Root partition not found" return 'Root partition not found'
if boot_partition is None: if boot_partition is None:
return "Boot partition not found" return 'Boot partition not found'
if SysInfo.has_uefi(): if SysInfo.has_uefi():
if efi_partition is None: if efi_partition is None:
return "EFI system partition (ESP) not found" return 'EFI system partition (ESP) not found'
if efi_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]: if efi_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]:
return "ESP must be formatted as a FAT filesystem" return 'ESP must be formatted as a FAT filesystem'
if bootloader == Bootloader.Limine: if bootloader == Bootloader.Limine:
if boot_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]: if boot_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]:
return "Limine does not support booting with a non-FAT boot partition" return 'Limine does not support booting with a non-FAT boot partition'
return None return None
def _prev_install_invalid_config(self, item: MenuItem) -> str | None: def _prev_install_invalid_config(self, item: MenuItem) -> str | None:
if missing := self._missing_configs(): if missing := self._missing_configs():
text = tr("Missing configurations:\n") text = tr('Missing configurations:\n')
for m in missing: for m in missing:
text += f"- {m}\n" text += f'- {m}\n'
return text[:-1] # remove last new line return text[:-1] # remove last new line
if error := self._validate_bootloader(): if error := self._validate_bootloader():
return tr(f"Invalid configuration: {error}") return tr(f'Invalid configuration: {error}')
return None return None
@ -484,24 +484,24 @@ class GlobalMenu(AbstractMenu[None]):
profile_config: ProfileConfiguration | None = item.value profile_config: ProfileConfiguration | None = item.value
if profile_config and profile_config.profile: if profile_config and profile_config.profile:
output = tr("Profiles") + ": " output = tr('Profiles') + ': '
if profile_names := profile_config.profile.current_selection_names(): if profile_names := profile_config.profile.current_selection_names():
output += ", ".join(profile_names) + "\n" output += ', '.join(profile_names) + '\n'
else: else:
output += profile_config.profile.name + "\n" output += profile_config.profile.name + '\n'
if profile_config.gfx_driver: if profile_config.gfx_driver:
output += tr("Graphics driver") + ": " + profile_config.gfx_driver.value + "\n" output += tr('Graphics driver') + ': ' + profile_config.gfx_driver.value + '\n'
if profile_config.greeter: if profile_config.greeter:
output += tr("Greeter") + ": " + profile_config.greeter.value + "\n" output += tr('Greeter') + ': ' + profile_config.greeter.value + '\n'
return output return output
return None return None
def _set_root_password(self, preset: str | None = None) -> Password | None: def _set_root_password(self, preset: str | None = None) -> Password | None:
password = get_password(text=tr("Root password"), allow_skip=True) password = get_password(text=tr('Root password'), allow_skip=True)
return password return password
def _select_disk_config( def _select_disk_config(
@ -511,7 +511,7 @@ class GlobalMenu(AbstractMenu[None]):
disk_config = DiskLayoutConfigurationMenu(preset).run() disk_config = DiskLayoutConfigurationMenu(preset).run()
if disk_config != preset: if disk_config != preset:
self._menu_item_group.find_by_key("disk_encryption").value = None self._menu_item_group.find_by_key('disk_encryption').value = None
return disk_config return disk_config
@ -519,7 +519,7 @@ class GlobalMenu(AbstractMenu[None]):
bootloader = ask_for_bootloader(preset) bootloader = ask_for_bootloader(preset)
if bootloader: if bootloader:
uki = self._item_group.find_by_key("uki") uki = self._item_group.find_by_key('uki')
if not SysInfo.has_uefi() or not bootloader.has_uki_support(): if not SysInfo.has_uefi() or not bootloader.has_uki_support():
uki.value = False uki.value = False
uki.enabled = False uki.enabled = False
@ -535,7 +535,7 @@ class GlobalMenu(AbstractMenu[None]):
return profile_config return profile_config
def _select_additional_packages(self, preset: list[str]) -> list[str]: def _select_additional_packages(self, preset: list[str]) -> list[str]:
config: MirrorConfiguration | None = self._item_group.find_by_key("mirror_config").value config: MirrorConfiguration | None = self._item_group.find_by_key('mirror_config').value
repositories: set[Repository] = set() repositories: set[Repository] = set()
if config: if config:
@ -574,28 +574,28 @@ class GlobalMenu(AbstractMenu[None]):
mirror_config: MirrorConfiguration = item.value mirror_config: MirrorConfiguration = item.value
output = "" output = ''
if mirror_config.mirror_regions: if mirror_config.mirror_regions:
title = tr("Selected mirror regions") title = tr('Selected mirror regions')
divider = "-" * len(title) divider = '-' * len(title)
regions = mirror_config.region_names regions = mirror_config.region_names
output += f"{title}\n{divider}\n{regions}\n\n" output += f'{title}\n{divider}\n{regions}\n\n'
if mirror_config.custom_servers: if mirror_config.custom_servers:
title = tr("Custom servers") title = tr('Custom servers')
divider = "-" * len(title) divider = '-' * len(title)
servers = mirror_config.custom_server_urls servers = mirror_config.custom_server_urls
output += f"{title}\n{divider}\n{servers}\n\n" output += f'{title}\n{divider}\n{servers}\n\n'
if mirror_config.optional_repositories: if mirror_config.optional_repositories:
title = tr("Optional repositories") title = tr('Optional repositories')
divider = "-" * len(title) divider = '-' * len(title)
repos = ", ".join([r.value for r in mirror_config.optional_repositories]) repos = ', '.join([r.value for r in mirror_config.optional_repositories])
output += f"{title}\n{divider}\n{repos}\n\n" output += f'{title}\n{divider}\n{repos}\n\n'
if mirror_config.custom_repositories: if mirror_config.custom_repositories:
title = tr("Custom repositories") title = tr('Custom repositories')
table = FormattedOutput.as_table(mirror_config.custom_repositories) table = FormattedOutput.as_table(mirror_config.custom_repositories)
output += f"{title}:\n\n{table}" output += f'{title}:\n\n{table}'
return output.strip() return output.strip()

View File

@ -11,12 +11,12 @@ from .translationhandler import tr
class CpuVendor(Enum): class CpuVendor(Enum):
AuthenticAMD = "amd" AuthenticAMD = 'amd'
GenuineIntel = "intel" GenuineIntel = 'intel'
_Unknown = "unknown" _Unknown = 'unknown'
@classmethod @classmethod
def get_vendor(cls, name: str) -> "CpuVendor": def get_vendor(cls, name: str) -> 'CpuVendor':
if vendor := getattr(cls, name, None): if vendor := getattr(cls, name, None):
return vendor return vendor
else: else:
@ -32,38 +32,38 @@ class CpuVendor(Enum):
def get_ucode(self) -> Path | None: def get_ucode(self) -> Path | None:
if self._has_microcode(): if self._has_microcode():
return Path(self.value + "-ucode.img") return Path(self.value + '-ucode.img')
return None return None
class GfxPackage(Enum): class GfxPackage(Enum):
Dkms = "dkms" Dkms = 'dkms'
IntelMediaDriver = "intel-media-driver" IntelMediaDriver = 'intel-media-driver'
LibvaIntelDriver = "libva-intel-driver" LibvaIntelDriver = 'libva-intel-driver'
LibvaMesaDriver = "libva-mesa-driver" LibvaMesaDriver = 'libva-mesa-driver'
LibvaNvidiaDriver = "libva-nvidia-driver" LibvaNvidiaDriver = 'libva-nvidia-driver'
Mesa = "mesa" Mesa = 'mesa'
NvidiaDkms = "nvidia-dkms" NvidiaDkms = 'nvidia-dkms'
NvidiaOpenDkms = "nvidia-open-dkms" NvidiaOpenDkms = 'nvidia-open-dkms'
VulkanIntel = "vulkan-intel" VulkanIntel = 'vulkan-intel'
VulkanRadeon = "vulkan-radeon" VulkanRadeon = 'vulkan-radeon'
VulkanNouveau = "vulkan-nouveau" VulkanNouveau = 'vulkan-nouveau'
Xf86VideoAmdgpu = "xf86-video-amdgpu" Xf86VideoAmdgpu = 'xf86-video-amdgpu'
Xf86VideoAti = "xf86-video-ati" Xf86VideoAti = 'xf86-video-ati'
Xf86VideoNouveau = "xf86-video-nouveau" Xf86VideoNouveau = 'xf86-video-nouveau'
Xf86VideoVmware = "xf86-video-vmware" Xf86VideoVmware = 'xf86-video-vmware'
XorgServer = "xorg-server" XorgServer = 'xorg-server'
XorgXinit = "xorg-xinit" XorgXinit = 'xorg-xinit'
class GfxDriver(Enum): class GfxDriver(Enum):
AllOpenSource = "All open-source" AllOpenSource = 'All open-source'
AmdOpenSource = "AMD / ATI (open-source)" AmdOpenSource = 'AMD / ATI (open-source)'
IntelOpenSource = "Intel (open-source)" IntelOpenSource = 'Intel (open-source)'
NvidiaOpenKernel = "Nvidia (open kernel module for newer GPUs, Turing+)" NvidiaOpenKernel = 'Nvidia (open kernel module for newer GPUs, Turing+)'
NvidiaOpenSource = "Nvidia (open-source nouveau driver)" NvidiaOpenSource = 'Nvidia (open-source nouveau driver)'
NvidiaProprietary = "Nvidia (proprietary)" NvidiaProprietary = 'Nvidia (proprietary)'
VMOpenSource = "VMware / VirtualBox (open-source)" VMOpenSource = 'VMware / VirtualBox (open-source)'
def is_nvidia(self) -> bool: def is_nvidia(self) -> bool:
match self: match self:
@ -74,10 +74,10 @@ class GfxDriver(Enum):
def packages_text(self) -> str: def packages_text(self) -> str:
pkg_names = [p.value for p in self.gfx_packages()] pkg_names = [p.value for p in self.gfx_packages()]
text = tr("Installed packages") + ":\n" text = tr('Installed packages') + ':\n'
for p in sorted(pkg_names): for p in sorted(pkg_names):
text += f"\t- {p}\n" text += f'\t- {p}\n'
return text return text
@ -151,13 +151,13 @@ class _SysInfo:
""" """
Returns system cpu information Returns system cpu information
""" """
cpu_info_path = Path("/proc/cpuinfo") cpu_info_path = Path('/proc/cpuinfo')
cpu: dict[str, str] = {} cpu: dict[str, str] = {}
with cpu_info_path.open() as file: with cpu_info_path.open() as file:
for line in file: for line in file:
if line := line.strip(): if line := line.strip():
key, value = line.split(":", maxsplit=1) key, value = line.split(':', maxsplit=1)
cpu[key.strip()] = value.strip() cpu[key.strip()] = value.strip()
return cpu return cpu
@ -167,12 +167,12 @@ class _SysInfo:
""" """
Returns system memory information Returns system memory information
""" """
mem_info_path = Path("/proc/meminfo") mem_info_path = Path('/proc/meminfo')
mem_info: dict[str, int] = {} mem_info: dict[str, int] = {}
with mem_info_path.open() as file: with mem_info_path.open() as file:
for line in file: for line in file:
key, value = line.strip().split(":") key, value = line.strip().split(':')
num = value.split()[0] num = value.split()[0]
mem_info[key] = int(num) mem_info[key] = int(num)
@ -186,7 +186,7 @@ class _SysInfo:
""" """
Returns loaded kernel modules Returns loaded kernel modules
""" """
modules_path = Path("/proc/modules") modules_path = Path('/proc/modules')
modules: list[str] = [] modules: list[str] = []
with modules_path.open() as file: with modules_path.open() as file:
@ -204,113 +204,113 @@ class SysInfo:
@staticmethod @staticmethod
def has_wifi() -> bool: def has_wifi() -> bool:
ifaces = list(list_interfaces().values()) ifaces = list(list_interfaces().values())
return "WIRELESS" in enrich_iface_types(ifaces).values() return 'WIRELESS' in enrich_iface_types(ifaces).values()
@staticmethod @staticmethod
def has_uefi() -> bool: def has_uefi() -> bool:
return os.path.isdir("/sys/firmware/efi") return os.path.isdir('/sys/firmware/efi')
@staticmethod @staticmethod
def _graphics_devices() -> dict[str, str]: def _graphics_devices() -> dict[str, str]:
cards: dict[str, str] = {} cards: dict[str, str] = {}
for line in SysCommand("lspci"): for line in SysCommand('lspci'):
if b" VGA " in line or b" 3D " in line: if b' VGA ' in line or b' 3D ' in line:
_, identifier = line.split(b": ", 1) _, identifier = line.split(b': ', 1)
cards[identifier.strip().decode("UTF-8")] = str(line) cards[identifier.strip().decode('UTF-8')] = str(line)
return cards return cards
@staticmethod @staticmethod
def has_nvidia_graphics() -> bool: def has_nvidia_graphics() -> bool:
return any("nvidia" in x.lower() for x in SysInfo._graphics_devices()) return any('nvidia' in x.lower() for x in SysInfo._graphics_devices())
@staticmethod @staticmethod
def has_amd_graphics() -> bool: def has_amd_graphics() -> bool:
return any("amd" in x.lower() for x in SysInfo._graphics_devices()) return any('amd' in x.lower() for x in SysInfo._graphics_devices())
@staticmethod @staticmethod
def has_intel_graphics() -> bool: def has_intel_graphics() -> bool:
return any("intel" in x.lower() for x in SysInfo._graphics_devices()) return any('intel' in x.lower() for x in SysInfo._graphics_devices())
@staticmethod @staticmethod
def cpu_vendor() -> CpuVendor | None: def cpu_vendor() -> CpuVendor | None:
if vendor := _sys_info.cpu_info.get("vendor_id"): if vendor := _sys_info.cpu_info.get('vendor_id'):
return CpuVendor.get_vendor(vendor) return CpuVendor.get_vendor(vendor)
return None return None
@staticmethod @staticmethod
def cpu_model() -> str | None: def cpu_model() -> str | None:
return _sys_info.cpu_info.get("model name", None) return _sys_info.cpu_info.get('model name', None)
@staticmethod @staticmethod
def sys_vendor() -> str: def sys_vendor() -> str:
with open("/sys/devices/virtual/dmi/id/sys_vendor") as vendor: with open('/sys/devices/virtual/dmi/id/sys_vendor') as vendor:
return vendor.read().strip() return vendor.read().strip()
@staticmethod @staticmethod
def product_name() -> str: def product_name() -> str:
with open("/sys/devices/virtual/dmi/id/product_name") as product: with open('/sys/devices/virtual/dmi/id/product_name') as product:
return product.read().strip() return product.read().strip()
@staticmethod @staticmethod
def mem_available() -> int: def mem_available() -> int:
return _sys_info.mem_info_by_key("MemAvailable") return _sys_info.mem_info_by_key('MemAvailable')
@staticmethod @staticmethod
def mem_free() -> int: def mem_free() -> int:
return _sys_info.mem_info_by_key("MemFree") return _sys_info.mem_info_by_key('MemFree')
@staticmethod @staticmethod
def mem_total() -> int: def mem_total() -> int:
return _sys_info.mem_info_by_key("MemTotal") return _sys_info.mem_info_by_key('MemTotal')
@staticmethod @staticmethod
def virtualization() -> str | None: def virtualization() -> str | None:
try: try:
return str(SysCommand("systemd-detect-virt")).strip("\r\n") return str(SysCommand('systemd-detect-virt')).strip('\r\n')
except SysCallError as err: except SysCallError as err:
debug(f"Could not detect virtual system: {err}") debug(f'Could not detect virtual system: {err}')
return None return None
@staticmethod @staticmethod
def is_vm() -> bool: def is_vm() -> bool:
try: try:
result = SysCommand("systemd-detect-virt") result = SysCommand('systemd-detect-virt')
return b"none" not in b"".join(result).lower() return b'none' not in b''.join(result).lower()
except SysCallError as err: except SysCallError as err:
debug(f"System is not running in a VM: {err}") debug(f'System is not running in a VM: {err}')
return False return False
@staticmethod @staticmethod
def requires_sof_fw() -> bool: def requires_sof_fw() -> bool:
return "snd_sof" in _sys_info.loaded_modules return 'snd_sof' in _sys_info.loaded_modules
@staticmethod @staticmethod
def requires_alsa_fw() -> bool: def requires_alsa_fw() -> bool:
modules = ( modules = (
"snd_asihpi", 'snd_asihpi',
"snd_cs46xx", 'snd_cs46xx',
"snd_darla20", 'snd_darla20',
"snd_darla24", 'snd_darla24',
"snd_echo3g", 'snd_echo3g',
"snd_emu10k1", 'snd_emu10k1',
"snd_gina20", 'snd_gina20',
"snd_gina24", 'snd_gina24',
"snd_hda_codec_ca0132", 'snd_hda_codec_ca0132',
"snd_hdsp", 'snd_hdsp',
"snd_indigo", 'snd_indigo',
"snd_indigodj", 'snd_indigodj',
"snd_indigodjx", 'snd_indigodjx',
"snd_indigoio", 'snd_indigoio',
"snd_indigoiox", 'snd_indigoiox',
"snd_layla20", 'snd_layla20',
"snd_layla24", 'snd_layla24',
"snd_mia", 'snd_mia',
"snd_mixart", 'snd_mixart',
"snd_mona", 'snd_mona',
"snd_pcxhr", 'snd_pcxhr',
"snd_vx_lib", 'snd_vx_lib',
) )
for loaded_module in _sys_info.loaded_modules: for loaded_module in _sys_info.loaded_modules:

File diff suppressed because it is too large Load Diff

View File

@ -20,26 +20,26 @@ from .network_menu import ManualNetworkConfig, ask_to_configure_network
from .system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_driver, select_kernel from .system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_driver, select_kernel
__all__ = [ __all__ = [
"ManualNetworkConfig", 'ManualNetworkConfig',
"UserList", 'UserList',
"add_number_of_parallel_downloads", 'add_number_of_parallel_downloads',
"ask_additional_packages_to_install", 'ask_additional_packages_to_install',
"ask_for_a_timezone", 'ask_for_a_timezone',
"ask_for_additional_users", 'ask_for_additional_users',
"ask_for_audio_selection", 'ask_for_audio_selection',
"ask_for_bootloader", 'ask_for_bootloader',
"ask_for_swap", 'ask_for_swap',
"ask_for_uki", 'ask_for_uki',
"ask_hostname", 'ask_hostname',
"ask_ntp", 'ask_ntp',
"ask_to_configure_network", 'ask_to_configure_network',
"get_default_partition_layout", 'get_default_partition_layout',
"select_archinstall_language", 'select_archinstall_language',
"select_devices", 'select_devices',
"select_disk_config", 'select_disk_config',
"select_driver", 'select_driver',
"select_kernel", 'select_kernel',
"select_main_filesystem_format", 'select_main_filesystem_format',
"suggest_multi_disk_layout", 'suggest_multi_disk_layout',
"suggest_single_disk_layout", 'suggest_single_disk_layout',
] ]

View File

@ -63,8 +63,8 @@ def select_devices(preset: list[BDevice] | None = []) -> list[BDevice]:
search_enabled=False, search_enabled=False,
multi=True, multi=True,
preview_style=PreviewStyle.BOTTOM, preview_style=PreviewStyle.BOTTOM,
preview_size="auto", preview_size='auto',
preview_frame=FrameProperties.max("Partitions"), preview_frame=FrameProperties.max('Partitions'),
allow_skip=True, allow_skip=True,
).run() ).run()
@ -136,7 +136,7 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
group, group,
allow_skip=True, allow_skip=True,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Disk configuration type")), frame=FrameProperties.min(tr('Disk configuration type')),
allow_reset=True, allow_reset=True,
).run() ).run()
@ -149,10 +149,10 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
selection = result.get_value() selection = result.get_value()
if selection == pre_mount_mode: if selection == pre_mount_mode:
output = "You will use whatever drive-setup is mounted at the specified directory\n" output = 'You will use whatever drive-setup is mounted at the specified directory\n'
output += "WARNING: Archinstall won't check the suitability of this setup\n" output += "WARNING: Archinstall won't check the suitability of this setup\n"
path = prompt_dir(tr("Root mount directory"), output, allow_skip=True) path = prompt_dir(tr('Root mount directory'), output, allow_skip=True)
if path is None: if path is None:
return None return None
@ -206,7 +206,7 @@ def select_lvm_config(
group, group,
allow_reset=True, allow_reset=True,
allow_skip=True, allow_skip=True,
frame=FrameProperties.min(tr("LVM configuration type")), frame=FrameProperties.min(tr('LVM configuration type')),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
).run() ).run()
@ -235,7 +235,7 @@ def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModifi
type=PartitionType.Primary, type=PartitionType.Primary,
start=start, start=start,
length=size, length=size,
mountpoint=Path("/boot"), mountpoint=Path('/boot'),
fs_type=FilesystemType.Fat32, fs_type=FilesystemType.Fat32,
flags=flags, flags=flags,
) )
@ -243,20 +243,20 @@ def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModifi
def select_main_filesystem_format() -> FilesystemType: def select_main_filesystem_format() -> FilesystemType:
items = [ items = [
MenuItem("btrfs", value=FilesystemType.Btrfs), MenuItem('btrfs', value=FilesystemType.Btrfs),
MenuItem("ext4", value=FilesystemType.Ext4), MenuItem('ext4', value=FilesystemType.Ext4),
MenuItem("xfs", value=FilesystemType.Xfs), MenuItem('xfs', value=FilesystemType.Xfs),
MenuItem("f2fs", value=FilesystemType.F2fs), MenuItem('f2fs', value=FilesystemType.F2fs),
] ]
if arch_config_handler.args.advanced: if arch_config_handler.args.advanced:
items.append(MenuItem("ntfs", value=FilesystemType.Ntfs)) items.append(MenuItem('ntfs', value=FilesystemType.Ntfs))
group = MenuItemGroup(items, sort_items=False) group = MenuItemGroup(items, sort_items=False)
result = SelectMenu[FilesystemType]( result = SelectMenu[FilesystemType](
group, group,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min("Filesystem"), frame=FrameProperties.min('Filesystem'),
allow_skip=False, allow_skip=False,
).run() ).run()
@ -264,13 +264,13 @@ def select_main_filesystem_format() -> FilesystemType:
case ResultType.Selection: case ResultType.Selection:
return result.get_value() return result.get_value()
case _: case _:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
def select_mount_options() -> list[str]: def select_mount_options() -> list[str]:
prompt = tr("Would you like to use compression or disable CoW?") + "\n" prompt = tr('Would you like to use compression or disable CoW?') + '\n'
compression = tr("Use compression") compression = tr('Use compression')
disable_cow = tr("Disable Copy-on-Write") disable_cow = tr('Disable Copy-on-Write')
items = [ items = [
MenuItem(compression, value=BtrfsMountOption.compress.value), MenuItem(compression, value=BtrfsMountOption.compress.value),
@ -293,7 +293,7 @@ def select_mount_options() -> list[str]:
case ResultType.Selection: case ResultType.Selection:
return [result.get_value()] return [result.get_value()]
case _: case _:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
def process_root_partition_size(total_size: Size, sector_size: SectorSize) -> Size: def process_root_partition_size(total_size: Size, sector_size: SectorSize) -> Size:
@ -325,7 +325,7 @@ def suggest_single_disk_layout(
min_size_to_allow_home_part = Size(64, Unit.GiB, sector_size) min_size_to_allow_home_part = Size(64, Unit.GiB, sector_size)
if filesystem_type == FilesystemType.Btrfs: if filesystem_type == FilesystemType.Btrfs:
prompt = tr("Would you like to use BTRFS subvolumes with a default structure?") + "\n" prompt = tr('Would you like to use BTRFS subvolumes with a default structure?') + '\n'
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
group.set_focus_by_value(MenuItem.yes().value) group.set_focus_by_value(MenuItem.yes().value)
result = SelectMenu[bool]( result = SelectMenu[bool](
@ -362,7 +362,7 @@ def suggest_single_disk_layout(
elif separate_home: elif separate_home:
using_home_partition = True using_home_partition = True
else: else:
prompt = tr("Would you like to create a separate partition for /home?") + "\n" prompt = tr('Would you like to create a separate partition for /home?') + '\n'
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
group.set_focus_by_value(MenuItem.yes().value) group.set_focus_by_value(MenuItem.yes().value)
result = SelectMenu( result = SelectMenu(
@ -390,7 +390,7 @@ def suggest_single_disk_layout(
type=PartitionType.Primary, type=PartitionType.Primary,
start=root_start, start=root_start,
length=root_length, length=root_length,
mountpoint=Path("/") if not using_subvolumes else None, mountpoint=Path('/') if not using_subvolumes else None,
fs_type=filesystem_type, fs_type=filesystem_type,
mount_options=mount_options, mount_options=mount_options,
) )
@ -402,10 +402,10 @@ def suggest_single_disk_layout(
# https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash # https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash
# https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh # https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh
subvolumes = [ subvolumes = [
SubvolumeModification(Path("@"), Path("/")), SubvolumeModification(Path('@'), Path('/')),
SubvolumeModification(Path("@home"), Path("/home")), SubvolumeModification(Path('@home'), Path('/home')),
SubvolumeModification(Path("@log"), Path("/var/log")), SubvolumeModification(Path('@log'), Path('/var/log')),
SubvolumeModification(Path("@pkg"), Path("/var/cache/pacman/pkg")), SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
] ]
root_partition.btrfs_subvols = subvolumes root_partition.btrfs_subvols = subvolumes
elif using_home_partition: elif using_home_partition:
@ -424,7 +424,7 @@ def suggest_single_disk_layout(
type=PartitionType.Primary, type=PartitionType.Primary,
start=home_start, start=home_start,
length=home_length, length=home_length,
mountpoint=Path("/home"), mountpoint=Path('/home'),
fs_type=filesystem_type, fs_type=filesystem_type,
mount_options=mount_options, mount_options=mount_options,
flags=flags, flags=flags,
@ -467,11 +467,11 @@ def suggest_multi_disk_layout(
root_device: BDevice | None = sorted_delta[0][0] root_device: BDevice | None = sorted_delta[0][0]
if home_device is None or root_device is None: if home_device is None or root_device is None:
text = tr("The selected drives do not have the minimum capacity required for an automatic suggestion\n") text = tr('The selected drives do not have the minimum capacity required for an automatic suggestion\n')
text += tr("Minimum capacity for /home partition: {}GiB\n").format(min_home_partition_size.format_size(Unit.GiB)) text += tr('Minimum capacity for /home partition: {}GiB\n').format(min_home_partition_size.format_size(Unit.GiB))
text += tr("Minimum capacity for Arch Linux partition: {}GiB").format(desired_root_partition_size.format_size(Unit.GiB)) text += tr('Minimum capacity for Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(Unit.GiB))
items = [MenuItem(tr("Continue"))] items = [MenuItem(tr('Continue'))]
group = MenuItemGroup(items) group = MenuItemGroup(items)
SelectMenu(group).run() SelectMenu(group).run()
@ -480,11 +480,11 @@ def suggest_multi_disk_layout(
if filesystem_type == FilesystemType.Btrfs: if filesystem_type == FilesystemType.Btrfs:
mount_options = select_mount_options() mount_options = select_mount_options()
device_paths = ", ".join([str(d.device_info.path) for d in devices]) device_paths = ', '.join([str(d.device_info.path) for d in devices])
debug(f"Suggesting multi-disk-layout for devices: {device_paths}") debug(f'Suggesting multi-disk-layout for devices: {device_paths}')
debug(f"/root: {root_device.device_info.path}") debug(f'/root: {root_device.device_info.path}')
debug(f"/home: {home_device.device_info.path}") debug(f'/home: {home_device.device_info.path}')
root_device_modification = DeviceModification(root_device, wipe=True) root_device_modification = DeviceModification(root_device, wipe=True)
home_device_modification = DeviceModification(home_device, wipe=True) home_device_modification = DeviceModification(home_device, wipe=True)
@ -512,7 +512,7 @@ def suggest_multi_disk_layout(
type=PartitionType.Primary, type=PartitionType.Primary,
start=root_start, start=root_start,
length=root_length, length=root_length,
mountpoint=Path("/"), mountpoint=Path('/'),
mount_options=mount_options, mount_options=mount_options,
fs_type=filesystem_type, fs_type=filesystem_type,
) )
@ -534,7 +534,7 @@ def suggest_multi_disk_layout(
type=PartitionType.Primary, type=PartitionType.Primary,
start=home_start, start=home_start,
length=home_length, length=home_length,
mountpoint=Path("/home"), mountpoint=Path('/home'),
mount_options=mount_options, mount_options=mount_options,
fs_type=filesystem_type, fs_type=filesystem_type,
flags=flags, flags=flags,
@ -547,10 +547,10 @@ def suggest_multi_disk_layout(
def suggest_lvm_layout( def suggest_lvm_layout(
disk_config: DiskLayoutConfiguration, disk_config: DiskLayoutConfiguration,
filesystem_type: FilesystemType | None = None, filesystem_type: FilesystemType | None = None,
vg_grp_name: str = "ArchinstallVg", vg_grp_name: str = 'ArchinstallVg',
) -> LvmConfiguration: ) -> LvmConfiguration:
if disk_config.config_type != DiskLayoutType.Default: if disk_config.config_type != DiskLayoutType.Default:
raise ValueError("LVM suggested volumes are only available for default partitioning") raise ValueError('LVM suggested volumes are only available for default partitioning')
using_subvolumes = False using_subvolumes = False
btrfs_subvols = [] btrfs_subvols = []
@ -561,7 +561,7 @@ def suggest_lvm_layout(
filesystem_type = select_main_filesystem_format() filesystem_type = select_main_filesystem_format()
if filesystem_type == FilesystemType.Btrfs: if filesystem_type == FilesystemType.Btrfs:
prompt = tr("Would you like to use BTRFS subvolumes with a default structure?") + "\n" prompt = tr('Would you like to use BTRFS subvolumes with a default structure?') + '\n'
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
group.set_focus_by_value(MenuItem.yes().value) group.set_focus_by_value(MenuItem.yes().value)
@ -580,10 +580,10 @@ def suggest_lvm_layout(
if using_subvolumes: if using_subvolumes:
btrfs_subvols = [ btrfs_subvols = [
SubvolumeModification(Path("@"), Path("/")), SubvolumeModification(Path('@'), Path('/')),
SubvolumeModification(Path("@home"), Path("/home")), SubvolumeModification(Path('@home'), Path('/home')),
SubvolumeModification(Path("@log"), Path("/var/log")), SubvolumeModification(Path('@log'), Path('/var/log')),
SubvolumeModification(Path("@pkg"), Path("/var/cache/pacman/pkg")), SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
] ]
home_volume = False home_volume = False
@ -599,7 +599,7 @@ def suggest_lvm_layout(
other_part.append(part) other_part.append(part)
if not boot_part: if not boot_part:
raise ValueError("Unable to find boot partition in partition modifications") raise ValueError('Unable to find boot partition in partition modifications')
total_vol_available = sum( total_vol_available = sum(
[p.length for p in other_part], [p.length for p in other_part],
@ -612,10 +612,10 @@ def suggest_lvm_layout(
root_vol = LvmVolume( root_vol = LvmVolume(
status=LvmVolumeStatus.Create, status=LvmVolumeStatus.Create,
name="root", name='root',
fs_type=filesystem_type, fs_type=filesystem_type,
length=root_vol_size, length=root_vol_size,
mountpoint=Path("/"), mountpoint=Path('/'),
btrfs_subvols=btrfs_subvols, btrfs_subvols=btrfs_subvols,
mount_options=mount_options, mount_options=mount_options,
) )
@ -625,10 +625,10 @@ def suggest_lvm_layout(
if home_volume: if home_volume:
home_vol = LvmVolume( home_vol = LvmVolume(
status=LvmVolumeStatus.Create, status=LvmVolumeStatus.Create,
name="home", name='home',
fs_type=filesystem_type, fs_type=filesystem_type,
length=home_vol_size, length=home_vol_size,
mountpoint=Path("/home"), mountpoint=Path('/home'),
) )
lvm_vol_group.volumes.append(home_vol) lvm_vol_group.volumes.append(home_vol)

View File

@ -20,18 +20,18 @@ from ..translationhandler import Language
class PostInstallationAction(Enum): class PostInstallationAction(Enum):
EXIT = tr("Exit archinstall") EXIT = tr('Exit archinstall')
REBOOT = tr("Reboot system") REBOOT = tr('Reboot system')
CHROOT = tr("chroot into installation for post-installation configurations") CHROOT = tr('chroot into installation for post-installation configurations')
def ask_ntp(preset: bool = True) -> bool: def ask_ntp(preset: bool = True) -> bool:
header = tr("Would you like to use automatic time synchronization (NTP) with the default time servers?\n") + "\n" header = tr('Would you like to use automatic time synchronization (NTP) with the default time servers?\n') + '\n'
header += ( header += (
tr( tr(
"Hardware time and other post-configuration steps might be required in order for NTP to work.\nFor more information, please check the Arch wiki", 'Hardware time and other post-configuration steps might be required in order for NTP to work.\nFor more information, please check the Arch wiki',
) )
+ "\n" + '\n'
) )
preset_val = MenuItem.yes() if preset else MenuItem.no() preset_val = MenuItem.yes() if preset else MenuItem.no()
@ -53,12 +53,12 @@ def ask_ntp(preset: bool = True) -> bool:
case ResultType.Selection: case ResultType.Selection:
return result.item() == MenuItem.yes() return result.item() == MenuItem.yes()
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
def ask_hostname(preset: str | None = None) -> str | None: def ask_hostname(preset: str | None = None) -> str | None:
result = EditMenu( result = EditMenu(
tr("Hostname"), tr('Hostname'),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
allow_skip=True, allow_skip=True,
default_text=preset, default_text=preset,
@ -73,11 +73,11 @@ def ask_hostname(preset: str | None = None) -> str | None:
return None return None
return hostname return hostname
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
def ask_for_a_timezone(preset: str | None = None) -> str | None: def ask_for_a_timezone(preset: str | None = None) -> str | None:
default = "UTC" default = 'UTC'
timezones = list_timezones() timezones = list_timezones()
items = [MenuItem(tz, value=tz) for tz in timezones] items = [MenuItem(tz, value=tz) for tz in timezones]
@ -89,7 +89,7 @@ def ask_for_a_timezone(preset: str | None = None) -> str | None:
group, group,
allow_reset=True, allow_reset=True,
allow_skip=True, allow_skip=True,
frame=FrameProperties.min(tr("Timezone")), frame=FrameProperties.min(tr('Timezone')),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
).run() ).run()
@ -113,7 +113,7 @@ def ask_for_audio_selection(preset: AudioConfiguration | None = None) -> AudioCo
group, group,
allow_skip=True, allow_skip=True,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Audio")), frame=FrameProperties.min(tr('Audio')),
).run() ).run()
match result.type_: match result.type_:
@ -122,7 +122,7 @@ def ask_for_audio_selection(preset: AudioConfiguration | None = None) -> AudioCo
case ResultType.Selection: case ResultType.Selection:
return AudioConfiguration(audio=result.get_value()) return AudioConfiguration(audio=result.get_value())
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
def select_language(preset: str | None = None) -> str | None: def select_language(preset: str | None = None) -> str | None:
@ -133,7 +133,7 @@ def select_language(preset: str | None = None) -> str | None:
# raise Deprecated("select_language() has been deprecated, use select_kb_layout() instead.") # raise Deprecated("select_language() has been deprecated, use select_kb_layout() instead.")
# No need to translate this i feel, as it's a short lived message. # No need to translate this i feel, as it's a short lived message.
warn("select_language() is deprecated, use select_kb_layout() instead. select_language() will be removed in a future version") warn('select_language() is deprecated, use select_kb_layout() instead. select_language() will be removed in a future version')
return select_kb_layout(preset) return select_kb_layout(preset)
@ -147,9 +147,9 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
group = MenuItemGroup(items, sort_items=True) group = MenuItemGroup(items, sort_items=True)
group.set_focus_by_value(preset) group.set_focus_by_value(preset)
title = "NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n" title = 'NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n'
title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n' title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n'
title += "e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n" title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n'
result = SelectMenu[Language]( result = SelectMenu[Language](
group, group,
@ -157,7 +157,7 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
allow_skip=True, allow_skip=True,
allow_reset=False, allow_reset=False,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(header=tr("Select language")), frame=FrameProperties.min(header=tr('Select language')),
).run() ).run()
match result.type_: match result.type_:
@ -166,7 +166,7 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
case ResultType.Selection: case ResultType.Selection:
return result.get_value() return result.get_value()
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Language selection not handled") raise ValueError('Language selection not handled')
def ask_additional_packages_to_install( def ask_additional_packages_to_install(
@ -175,18 +175,18 @@ def ask_additional_packages_to_install(
) -> list[str]: ) -> list[str]:
repositories |= {Repository.Core, Repository.Extra} repositories |= {Repository.Core, Repository.Extra}
respos_text = ", ".join([r.value for r in repositories]) respos_text = ', '.join([r.value for r in repositories])
output = tr("Repositories: {}").format(respos_text) + "\n" output = tr('Repositories: {}').format(respos_text) + '\n'
output += tr("Loading packages...") output += tr('Loading packages...')
Tui.print(output, clear_screen=True) Tui.print(output, clear_screen=True)
packages = list_available_packages(tuple(repositories)) packages = list_available_packages(tuple(repositories))
package_groups = PackageGroup.from_available_packages(packages) package_groups = PackageGroup.from_available_packages(packages)
# Additional packages (with some light weight error handling for invalid package names) # Additional packages (with some light weight error handling for invalid package names)
header = tr("Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.") + "\n" header = tr('Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.') + '\n'
header += tr("Select any packages from the below list that should be installed additionally") + "\n" header += tr('Select any packages from the below list that should be installed additionally') + '\n'
# there are over 15k packages so this needs to be quick # there are over 15k packages so this needs to be quick
preset_packages: list[AvailablePackage | PackageGroup] = [] preset_packages: list[AvailablePackage | PackageGroup] = []
@ -224,9 +224,9 @@ def ask_additional_packages_to_install(
allow_reset=True, allow_reset=True,
allow_skip=True, allow_skip=True,
multi=True, multi=True,
preview_frame=FrameProperties.max("Package info"), preview_frame=FrameProperties.max('Package info'),
preview_style=PreviewStyle.RIGHT, preview_style=PreviewStyle.RIGHT,
preview_size="auto", preview_size='auto',
).run() ).run()
match result.type_: match result.type_:
@ -242,10 +242,10 @@ def ask_additional_packages_to_install(
def add_number_of_parallel_downloads(preset: int | None = None) -> int | None: def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
max_recommended = 5 max_recommended = 5
header = tr("This option enables the number of parallel downloads that can occur during package downloads") + "\n" header = tr('This option enables the number of parallel downloads that can occur during package downloads') + '\n'
header += tr("Enter the number of parallel downloads to be enabled.\n\nNote:\n") header += tr('Enter the number of parallel downloads to be enabled.\n\nNote:\n')
header += tr(" - Maximum recommended value : {} ( Allows {} parallel downloads at a time )").format(max_recommended, max_recommended) + "\n" header += tr(' - Maximum recommended value : {} ( Allows {} parallel downloads at a time )').format(max_recommended, max_recommended) + '\n'
header += tr(" - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n") header += tr(' - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n')
def validator(s: str) -> str | None: def validator(s: str) -> str | None:
try: try:
@ -255,10 +255,10 @@ def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
except Exception: except Exception:
pass pass
return tr("Invalid download number") return tr('Invalid download number')
result = EditMenu( result = EditMenu(
tr("Number downloads"), tr('Number downloads'),
header=header, header=header,
allow_skip=True, allow_skip=True,
allow_reset=True, allow_reset=True,
@ -276,23 +276,23 @@ def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
case _: case _:
assert_never(result.type_) assert_never(result.type_)
pacman_conf_path = Path("/etc/pacman.conf") pacman_conf_path = Path('/etc/pacman.conf')
with pacman_conf_path.open() as f: with pacman_conf_path.open() as f:
pacman_conf = f.read().split("\n") pacman_conf = f.read().split('\n')
with pacman_conf_path.open("w") as fwrite: with pacman_conf_path.open('w') as fwrite:
for line in pacman_conf: for line in pacman_conf:
if "ParallelDownloads" in line: if 'ParallelDownloads' in line:
fwrite.write(f"ParallelDownloads = {downloads}\n") fwrite.write(f'ParallelDownloads = {downloads}\n')
else: else:
fwrite.write(f"{line}\n") fwrite.write(f'{line}\n')
return downloads return downloads
def ask_post_installation() -> PostInstallationAction: def ask_post_installation() -> PostInstallationAction:
header = tr("Installation completed") + "\n\n" header = tr('Installation completed') + '\n\n'
header += tr("What would you like to do next?") + "\n" header += tr('What would you like to do next?') + '\n'
items = [MenuItem(action.value, value=action) for action in PostInstallationAction] items = [MenuItem(action.value, value=action) for action in PostInstallationAction]
group = MenuItemGroup(items) group = MenuItemGroup(items)
@ -308,11 +308,11 @@ def ask_post_installation() -> PostInstallationAction:
case ResultType.Selection: case ResultType.Selection:
return result.get_value() return result.get_value()
case _: case _:
raise ValueError("Post installation action not handled") raise ValueError('Post installation action not handled')
def ask_abort() -> None: def ask_abort() -> None:
prompt = tr("Do you really want to abort?") + "\n" prompt = tr('Do you really want to abort?') + '\n'
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
result = SelectMenu[bool]( result = SelectMenu[bool](

View File

@ -17,10 +17,10 @@ from ..utils.util import get_password
class UserList(ListManager[User]): class UserList(ListManager[User]):
def __init__(self, prompt: str, lusers: list[User]): def __init__(self, prompt: str, lusers: list[User]):
self._actions = [ self._actions = [
tr("Add a user"), tr('Add a user'),
tr("Change password"), tr('Change password'),
tr("Promote/Demote user"), tr('Promote/Demote user'),
tr("Delete User"), tr('Delete User'),
] ]
super().__init__( super().__init__(
@ -44,8 +44,8 @@ class UserList(ListManager[User]):
data = [d for d in data if d.username != new_user.username] data = [d for d in data if d.username != new_user.username]
data += [new_user] data += [new_user]
elif action == self._actions[1] and entry: # change password elif action == self._actions[1] and entry: # change password
header = f"{tr('User')}: {entry.username}\n" header = f'{tr("User")}: {entry.username}\n'
new_password = get_password(tr("Password"), header=header) new_password = get_password(tr('Password'), header=header)
if new_password: if new_password:
user = next(filter(lambda x: x == entry, data)) user = next(filter(lambda x: x == entry, data))
@ -59,13 +59,13 @@ class UserList(ListManager[User]):
return data return data
def _check_for_correct_username(self, username: str) -> str | None: def _check_for_correct_username(self, username: str) -> str | None:
if re.match(r"^[a-z_][a-z0-9_-]*\$?$", username) and len(username) <= 32: if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32:
return None return None
return tr("The username you entered is invalid") return tr('The username you entered is invalid')
def _add_user(self) -> User | None: def _add_user(self) -> User | None:
editResult = EditMenu( editResult = EditMenu(
tr("Username"), tr('Username'),
allow_skip=True, allow_skip=True,
validator=self._check_for_correct_username, validator=self._check_for_correct_username,
).input() ).input()
@ -76,16 +76,16 @@ class UserList(ListManager[User]):
case ResultType.Selection: case ResultType.Selection:
username = editResult.text() username = editResult.text()
case _: case _:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
header = f"{tr('Username')}: {username}\n" header = f'{tr("Username")}: {username}\n'
password = get_password(tr("Password"), header=header, allow_skip=True) password = get_password(tr('Password'), header=header, allow_skip=True)
if not password: if not password:
return None return None
header += f"{tr('Password')}: {password.hidden()}\n\n" header += f'{tr("Password")}: {password.hidden()}\n\n'
header += str(tr('Should "{}" be a superuser (sudo)?\n')).format(username) header += str(tr('Should "{}" be a superuser (sudo)?\n')).format(username)
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
@ -105,11 +105,11 @@ class UserList(ListManager[User]):
case ResultType.Selection: case ResultType.Selection:
sudo = result.item() == MenuItem.yes() sudo = result.item() == MenuItem.yes()
case _: case _:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
return User(username, password, sudo) return User(username, password, sudo)
def ask_for_additional_users(prompt: str = "", defined_users: list[User] = []) -> list[User]: def ask_for_additional_users(prompt: str = '', defined_users: list[User] = []) -> list[User]:
users = UserList(prompt, defined_users).run() users = UserList(prompt, defined_users).run()
return users return users

View File

@ -17,9 +17,9 @@ from ..networking import list_interfaces
class ManualNetworkConfig(ListManager[Nic]): class ManualNetworkConfig(ListManager[Nic]):
def __init__(self, prompt: str, preset: list[Nic]): def __init__(self, prompt: str, preset: list[Nic]):
self._actions = [ self._actions = [
tr("Add interface"), tr('Add interface'),
tr("Edit interface"), tr('Edit interface'),
tr("Delete interface"), tr('Delete interface'),
] ]
super().__init__( super().__init__(
@ -31,7 +31,7 @@ class ManualNetworkConfig(ListManager[Nic]):
@override @override
def selected_action_display(self, selection: Nic) -> str: def selected_action_display(self, selection: Nic) -> str:
return selection.iface if selection.iface else "" return selection.iface if selection.iface else ''
@override @override
def handle_action(self, action: str, entry: Nic | None, data: list[Nic]) -> list[Nic]: def handle_action(self, action: str, entry: Nic | None, data: list[Nic]) -> list[Nic]:
@ -67,7 +67,7 @@ class ManualNetworkConfig(ListManager[Nic]):
result = SelectMenu[str]( result = SelectMenu[str](
group, group,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Interfaces")), frame=FrameProperties.min(tr('Interfaces')),
allow_skip=True, allow_skip=True,
).run() ).run()
@ -77,7 +77,7 @@ class ManualNetworkConfig(ListManager[Nic]):
case ResultType.Selection: case ResultType.Selection:
return result.get_value() return result.get_value()
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
def _get_ip_address( def _get_ip_address(
self, self,
@ -89,7 +89,7 @@ class ManualNetworkConfig(ListManager[Nic]):
) -> str | None: ) -> str | None:
def validator(ip: str) -> str | None: def validator(ip: str) -> str | None:
if multi: if multi:
ips = ip.split(" ") ips = ip.split(' ')
else: else:
ips = [ip] ips = [ip]
@ -98,7 +98,7 @@ class ManualNetworkConfig(ListManager[Nic]):
ipaddress.ip_interface(ip) ipaddress.ip_interface(ip)
return None return None
except ValueError: except ValueError:
return tr("You need to enter a valid IP in IP-config mode") return tr('You need to enter a valid IP in IP-config mode')
result = EditMenu( result = EditMenu(
title, title,
@ -114,14 +114,14 @@ class ManualNetworkConfig(ListManager[Nic]):
case ResultType.Selection: case ResultType.Selection:
return result.text() return result.text()
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
def _edit_iface(self, edit_nic: Nic) -> Nic: def _edit_iface(self, edit_nic: Nic) -> Nic:
iface_name = edit_nic.iface iface_name = edit_nic.iface
modes = ["DHCP (auto detect)", "IP (static)"] modes = ['DHCP (auto detect)', 'IP (static)']
default_mode = "DHCP (auto detect)" default_mode = 'DHCP (auto detect)'
header = tr('Select which mode to configure for "{}"').format(iface_name) + "\n" header = tr('Select which mode to configure for "{}"').format(iface_name) + '\n'
items = [MenuItem(m, value=m) for m in modes] items = [MenuItem(m, value=m) for m in modes]
group = MenuItemGroup(items, sort_items=True) group = MenuItemGroup(items, sort_items=True)
group.set_default_by_value(default_mode) group.set_default_by_value(default_mode)
@ -131,34 +131,34 @@ class ManualNetworkConfig(ListManager[Nic]):
header=header, header=header,
allow_skip=False, allow_skip=False,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Modes")), frame=FrameProperties.min(tr('Modes')),
).run() ).run()
match result.type_: match result.type_:
case ResultType.Selection: case ResultType.Selection:
mode = result.get_value() mode = result.get_value()
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
case ResultType.Skip: case ResultType.Skip:
raise ValueError("The mode menu should not be skippable") raise ValueError('The mode menu should not be skippable')
case _: case _:
assert_never(result.type_) assert_never(result.type_)
if mode == "IP (static)": if mode == 'IP (static)':
header = tr("Enter the IP and subnet for {} (example: 192.168.0.5/24): ").format(iface_name) + "\n" header = tr('Enter the IP and subnet for {} (example: 192.168.0.5/24): ').format(iface_name) + '\n'
ip = self._get_ip_address(tr("IP address"), header, False, False) ip = self._get_ip_address(tr('IP address'), header, False, False)
header = tr("Enter your gateway (router) IP address (leave blank for none)") + "\n" header = tr('Enter your gateway (router) IP address (leave blank for none)') + '\n'
gateway = self._get_ip_address(tr("Gateway address"), header, True, False) gateway = self._get_ip_address(tr('Gateway address'), header, True, False)
if edit_nic.dns: if edit_nic.dns:
display_dns = " ".join(edit_nic.dns) display_dns = ' '.join(edit_nic.dns)
else: else:
display_dns = None display_dns = None
header = tr("Enter your DNS servers with space separated (leave blank for none)") + "\n" header = tr('Enter your DNS servers with space separated (leave blank for none)') + '\n'
dns_servers = self._get_ip_address( dns_servers = self._get_ip_address(
tr("DNS servers"), tr('DNS servers'),
header, header,
True, True,
True, True,
@ -167,7 +167,7 @@ class ManualNetworkConfig(ListManager[Nic]):
dns = [] dns = []
if dns_servers is not None: if dns_servers is not None:
dns = dns_servers.split(" ") dns = dns_servers.split(' ')
return Nic(iface=iface_name, ip=ip, gateway=gateway, dns=dns, dhcp=False) return Nic(iface=iface_name, ip=ip, gateway=gateway, dns=dns, dhcp=False)
else: else:
@ -189,7 +189,7 @@ def ask_to_configure_network(preset: NetworkConfiguration | None) -> NetworkConf
result = SelectMenu[NetworkConfiguration]( result = SelectMenu[NetworkConfiguration](
group, group,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Network configuration")), frame=FrameProperties.min(tr('Network configuration')),
allow_reset=True, allow_reset=True,
allow_skip=True, allow_skip=True,
).run() ).run()
@ -209,7 +209,7 @@ def ask_to_configure_network(preset: NetworkConfiguration | None) -> NetworkConf
return NetworkConfiguration(NicType.NM) return NetworkConfiguration(NicType.NM)
case NicType.MANUAL: case NicType.MANUAL:
preset_nics = preset.nics if preset else [] preset_nics = preset.nics if preset else []
nics = ManualNetworkConfig(tr("Configure interfaces"), preset_nics).run() nics = ManualNetworkConfig(tr('Configure interfaces'), preset_nics).run()
if nics: if nics:
return NetworkConfiguration(NicType.MANUAL, nics) return NetworkConfiguration(NicType.MANUAL, nics)

View File

@ -17,8 +17,8 @@ def select_kernel(preset: list[str] = []) -> list[str]:
:return: The string as a selected kernel :return: The string as a selected kernel
:rtype: string :rtype: string
""" """
kernels = ["linux", "linux-lts", "linux-zen", "linux-hardened"] kernels = ['linux', 'linux-lts', 'linux-zen', 'linux-hardened']
default_kernel = "linux" default_kernel = 'linux'
items = [MenuItem(k, value=k) for k in kernels] items = [MenuItem(k, value=k) for k in kernels]
@ -32,7 +32,7 @@ def select_kernel(preset: list[str] = []) -> list[str]:
allow_skip=True, allow_skip=True,
allow_reset=True, allow_reset=True,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Kernel")), frame=FrameProperties.min(tr('Kernel')),
multi=True, multi=True,
).run() ).run()
@ -50,7 +50,7 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
if not SysInfo.has_uefi(): if not SysInfo.has_uefi():
options = [Bootloader.Grub, Bootloader.Limine] options = [Bootloader.Grub, Bootloader.Limine]
default = Bootloader.Grub default = Bootloader.Grub
header = tr("UEFI is not detected and some options are disabled") header = tr('UEFI is not detected and some options are disabled')
else: else:
options = [b for b in Bootloader] options = [b for b in Bootloader]
default = Bootloader.Systemd default = Bootloader.Systemd
@ -65,7 +65,7 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
group, group,
header=header, header=header,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Bootloader")), frame=FrameProperties.min(tr('Bootloader')),
allow_skip=True, allow_skip=True,
).run() ).run()
@ -75,11 +75,11 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
case ResultType.Selection: case ResultType.Selection:
return result.get_value() return result.get_value()
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
def ask_for_uki(preset: bool = True) -> bool: def ask_for_uki(preset: bool = True) -> bool:
prompt = tr("Would you like to use unified kernel images?") + "\n" prompt = tr('Would you like to use unified kernel images?') + '\n'
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
group.set_focus_by_value(preset) group.set_focus_by_value(preset)
@ -99,7 +99,7 @@ def ask_for_uki(preset: bool = True) -> bool:
case ResultType.Selection: case ResultType.Selection:
return result.item() == MenuItem.yes() return result.item() == MenuItem.yes()
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None) -> GfxDriver | None: def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None) -> GfxDriver | None:
@ -120,22 +120,22 @@ def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None
if preset is not None: if preset is not None:
group.set_focus_by_value(preset) group.set_focus_by_value(preset)
header = "" header = ''
if SysInfo.has_amd_graphics(): if SysInfo.has_amd_graphics():
header += tr("For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options.") + "\n" header += tr('For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options.') + '\n'
if SysInfo.has_intel_graphics(): if SysInfo.has_intel_graphics():
header += tr("For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n") header += tr('For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n')
if SysInfo.has_nvidia_graphics(): if SysInfo.has_nvidia_graphics():
header += tr("For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n") header += tr('For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n')
result = SelectMenu[GfxDriver]( result = SelectMenu[GfxDriver](
group, group,
header=header, header=header,
allow_skip=True, allow_skip=True,
allow_reset=True, allow_reset=True,
preview_size="auto", preview_size='auto',
preview_style=PreviewStyle.BOTTOM, preview_style=PreviewStyle.BOTTOM,
preview_frame=FrameProperties(tr("Info"), h_frame_style=FrameStyle.MIN), preview_frame=FrameProperties(tr('Info'), h_frame_style=FrameStyle.MIN),
).run() ).run()
match result.type_: match result.type_:
@ -153,7 +153,7 @@ def ask_for_swap(preset: bool = True) -> bool:
else: else:
default_item = MenuItem.no() default_item = MenuItem.no()
prompt = tr("Would you like to use swap on zram?") + "\n" prompt = tr('Would you like to use swap on zram?') + '\n'
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
group.set_focus_by_value(default_item) group.set_focus_by_value(default_item)
@ -173,6 +173,6 @@ def ask_for_swap(preset: bool = True) -> bool:
case ResultType.Selection: case ResultType.Selection:
return result.item() == MenuItem.yes() return result.item() == MenuItem.yes()
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
return preset return preset

View File

@ -9,11 +9,11 @@ from .utils import (
) )
__all__ = [ __all__ = [
"list_keyboard_languages", 'list_keyboard_languages',
"list_locales", 'list_locales',
"list_timezones", 'list_timezones',
"list_x11_keyboard_languages", 'list_x11_keyboard_languages',
"set_kb_layout", 'set_kb_layout',
"verify_keyboard_layout", 'verify_keyboard_layout',
"verify_x11_keyboard_layout", 'verify_x11_keyboard_layout',
] ]

View File

@ -29,33 +29,33 @@ class LocaleMenu(AbstractSubMenu[LocaleConfiguration]):
def _define_menu_options(self) -> list[MenuItem]: def _define_menu_options(self) -> list[MenuItem]:
return [ return [
MenuItem( MenuItem(
text=tr("Keyboard layout"), text=tr('Keyboard layout'),
action=self._select_kb_layout, action=self._select_kb_layout,
value=self._locale_conf.kb_layout, value=self._locale_conf.kb_layout,
preview_action=self._prev_locale, preview_action=self._prev_locale,
key="kb_layout", key='kb_layout',
), ),
MenuItem( MenuItem(
text=tr("Locale language"), text=tr('Locale language'),
action=select_locale_lang, action=select_locale_lang,
value=self._locale_conf.sys_lang, value=self._locale_conf.sys_lang,
preview_action=self._prev_locale, preview_action=self._prev_locale,
key="sys_lang", key='sys_lang',
), ),
MenuItem( MenuItem(
text=tr("Locale encoding"), text=tr('Locale encoding'),
action=select_locale_enc, action=select_locale_enc,
value=self._locale_conf.sys_enc, value=self._locale_conf.sys_enc,
preview_action=self._prev_locale, preview_action=self._prev_locale,
key="sys_enc", key='sys_enc',
), ),
] ]
def _prev_locale(self, item: MenuItem) -> str | None: def _prev_locale(self, item: MenuItem) -> str | None:
temp_locale = LocaleConfiguration( temp_locale = LocaleConfiguration(
self._menu_item_group.find_by_key("kb_layout").get_value(), self._menu_item_group.find_by_key('kb_layout').get_value(),
self._menu_item_group.find_by_key("sys_lang").get_value(), self._menu_item_group.find_by_key('sys_lang').get_value(),
self._menu_item_group.find_by_key("sys_enc").get_value(), self._menu_item_group.find_by_key('sys_enc').get_value(),
) )
return temp_locale.preview() return temp_locale.preview()
@ -82,7 +82,7 @@ def select_locale_lang(preset: str | None = None) -> str | None:
result = SelectMenu[str]( result = SelectMenu[str](
group, group,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Locale language")), frame=FrameProperties.min(tr('Locale language')),
allow_skip=True, allow_skip=True,
).run() ).run()
@ -92,7 +92,7 @@ def select_locale_lang(preset: str | None = None) -> str | None:
case ResultType.Skip: case ResultType.Skip:
return preset return preset
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
def select_locale_enc(preset: str | None = None) -> str | None: def select_locale_enc(preset: str | None = None) -> str | None:
@ -106,7 +106,7 @@ def select_locale_enc(preset: str | None = None) -> str | None:
result = SelectMenu[str]( result = SelectMenu[str](
group, group,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Locale encoding")), frame=FrameProperties.min(tr('Locale encoding')),
allow_skip=True, allow_skip=True,
).run() ).run()
@ -116,7 +116,7 @@ def select_locale_enc(preset: str | None = None) -> str | None:
case ResultType.Skip: case ResultType.Skip:
return preset return preset
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
def select_kb_layout(preset: str | None = None) -> str | None: def select_kb_layout(preset: str | None = None) -> str | None:
@ -138,7 +138,7 @@ def select_kb_layout(preset: str | None = None) -> str | None:
result = SelectMenu[str]( result = SelectMenu[str](
group, group,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Keyboard layout")), frame=FrameProperties.min(tr('Keyboard layout')),
allow_skip=True, allow_skip=True,
).run() ).run()
@ -148,6 +148,6 @@ def select_kb_layout(preset: str | None = None) -> str | None:
case ResultType.Skip: case ResultType.Skip:
return preset return preset
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
return None return None

View File

@ -6,8 +6,8 @@ from ..output import error
def list_keyboard_languages() -> list[str]: def list_keyboard_languages() -> list[str]:
return ( return (
SysCommand( SysCommand(
"localectl --no-pager list-keymaps", 'localectl --no-pager list-keymaps',
environment_vars={"SYSTEMD_COLORS": "0"}, environment_vars={'SYSTEMD_COLORS': '0'},
) )
.decode() .decode()
.splitlines() .splitlines()
@ -17,9 +17,9 @@ def list_keyboard_languages() -> list[str]:
def list_locales() -> list[str]: def list_locales() -> list[str]:
locales = [] locales = []
with open("/usr/share/i18n/SUPPORTED") as file: with open('/usr/share/i18n/SUPPORTED') as file:
for line in file: for line in file:
if line != "C.UTF-8 UTF-8\n": if line != 'C.UTF-8 UTF-8\n':
locales.append(line.rstrip()) locales.append(line.rstrip())
return locales return locales
@ -28,8 +28,8 @@ def list_locales() -> list[str]:
def list_x11_keyboard_languages() -> list[str]: def list_x11_keyboard_languages() -> list[str]:
return ( return (
SysCommand( SysCommand(
"localectl --no-pager list-x11-keymap-layouts", 'localectl --no-pager list-x11-keymap-layouts',
environment_vars={"SYSTEMD_COLORS": "0"}, environment_vars={'SYSTEMD_COLORS': '0'},
) )
.decode() .decode()
.splitlines() .splitlines()
@ -54,26 +54,26 @@ def get_kb_layout() -> str:
try: try:
lines = ( lines = (
SysCommand( SysCommand(
"localectl --no-pager status", 'localectl --no-pager status',
environment_vars={"SYSTEMD_COLORS": "0"}, environment_vars={'SYSTEMD_COLORS': '0'},
) )
.decode() .decode()
.splitlines() .splitlines()
) )
except Exception: except Exception:
return "" return ''
vcline = "" vcline = ''
for line in lines: for line in lines:
if "VC Keymap: " in line: if 'VC Keymap: ' in line:
vcline = line vcline = line
if vcline == "": if vcline == '':
return "" return ''
layout = vcline.split(": ")[1] layout = vcline.split(': ')[1]
if not verify_keyboard_layout(layout): if not verify_keyboard_layout(layout):
return "" return ''
return layout return layout
@ -81,11 +81,11 @@ def get_kb_layout() -> str:
def set_kb_layout(locale: str) -> bool: def set_kb_layout(locale: str) -> bool:
if len(locale.strip()): if len(locale.strip()):
if not verify_keyboard_layout(locale): if not verify_keyboard_layout(locale):
error(f"Invalid keyboard locale specified: {locale}") error(f'Invalid keyboard locale specified: {locale}')
return False return False
try: try:
SysCommand(f"localectl set-keymap {locale}") SysCommand(f'localectl set-keymap {locale}')
except SysCallError as err: except SysCallError as err:
raise ServiceException(f"Unable to set locale '{locale}' for console: {err}") raise ServiceException(f"Unable to set locale '{locale}' for console: {err}")
@ -97,8 +97,8 @@ def set_kb_layout(locale: str) -> bool:
def list_timezones() -> list[str]: def list_timezones() -> list[str]:
return ( return (
SysCommand( SysCommand(
"timedatectl --no-pager list-timezones", 'timedatectl --no-pager list-timezones',
environment_vars={"SYSTEMD_COLORS": "0"}, environment_vars={'SYSTEMD_COLORS': '0'},
) )
.decode() .decode()
.splitlines() .splitlines()

View File

@ -24,25 +24,25 @@ class Luks2:
@property @property
def mapper_dev(self) -> Path | None: def mapper_dev(self) -> Path | None:
if self.mapper_name: if self.mapper_name:
return Path(f"/dev/mapper/{self.mapper_name}") return Path(f'/dev/mapper/{self.mapper_name}')
return None return None
def isLuks(self) -> bool: def isLuks(self) -> bool:
try: try:
SysCommand(f"cryptsetup isLuks {self.luks_dev_path}") SysCommand(f'cryptsetup isLuks {self.luks_dev_path}')
return True return True
except SysCallError: except SysCallError:
return False return False
def erase(self) -> None: def erase(self) -> None:
debug(f"Erasing luks partition: {self.luks_dev_path}") debug(f'Erasing luks partition: {self.luks_dev_path}')
worker = SysCommandWorker(f"cryptsetup erase {self.luks_dev_path}") worker = SysCommandWorker(f'cryptsetup erase {self.luks_dev_path}')
worker.poll() worker.poll()
worker.write(b"YES\n", line_ending=False) worker.write(b'YES\n', line_ending=False)
def __post_init__(self) -> None: def __post_init__(self) -> None:
if self.luks_dev_path is None: if self.luks_dev_path is None:
raise ValueError("Partition must have a path set") raise ValueError('Partition must have a path set')
def __enter__(self) -> None: def __enter__(self) -> None:
self.unlock(self.key_file) self.unlock(self.key_file)
@ -53,12 +53,12 @@ class Luks2:
def _password_bytes(self) -> bytes: def _password_bytes(self) -> bytes:
if not self.password: if not self.password:
raise ValueError("Password for luks2 device was not specified") raise ValueError('Password for luks2 device was not specified')
if isinstance(self.password, bytes): if isinstance(self.password, bytes):
return self.password return self.password
else: else:
return bytes(self.password.plaintext, "UTF-8") return bytes(self.password.plaintext, 'UTF-8')
def _get_passphrase_args( def _get_passphrase_args(
self, self,
@ -67,42 +67,42 @@ class Luks2:
key_file = key_file or self.key_file key_file = key_file or self.key_file
if key_file: if key_file:
return ["--key-file", str(key_file)], None return ['--key-file', str(key_file)], None
return [], self._password_bytes() return [], self._password_bytes()
def encrypt( def encrypt(
self, self,
key_size: int = 512, key_size: int = 512,
hash_type: str = "sha512", hash_type: str = 'sha512',
iter_time: int = 10000, iter_time: int = 10000,
key_file: Path | None = None, key_file: Path | None = None,
) -> Path | None: ) -> Path | None:
debug(f"Luks2 encrypting: {self.luks_dev_path}") debug(f'Luks2 encrypting: {self.luks_dev_path}')
key_file_arg, passphrase = self._get_passphrase_args(key_file) key_file_arg, passphrase = self._get_passphrase_args(key_file)
cmd = [ cmd = [
"cryptsetup", 'cryptsetup',
"--batch-mode", '--batch-mode',
"--verbose", '--verbose',
"--type", '--type',
"luks2", 'luks2',
"--pbkdf", '--pbkdf',
"argon2id", 'argon2id',
"--hash", '--hash',
hash_type, hash_type,
"--key-size", '--key-size',
str(key_size), str(key_size),
"--iter-time", '--iter-time',
str(iter_time), str(iter_time),
*key_file_arg, *key_file_arg,
"--use-urandom", '--use-urandom',
"luksFormat", 'luksFormat',
str(self.luks_dev_path), str(self.luks_dev_path),
] ]
debug(f"cryptsetup format: {shlex.join(cmd)}") debug(f'cryptsetup format: {shlex.join(cmd)}')
try: try:
result = run(cmd, input_data=passphrase) result = run(cmd, input_data=passphrase)
@ -110,23 +110,23 @@ class Luks2:
output = err.stdout.decode().rstrip() output = err.stdout.decode().rstrip()
raise DiskError(f'Could not encrypt volume "{self.luks_dev_path}": {output}') raise DiskError(f'Could not encrypt volume "{self.luks_dev_path}": {output}')
debug(f"cryptsetup luksFormat output: {result.stdout.decode().rstrip()}") debug(f'cryptsetup luksFormat output: {result.stdout.decode().rstrip()}')
self.key_file = key_file self.key_file = key_file
return key_file return key_file
def _get_luks_uuid(self) -> str: def _get_luks_uuid(self) -> str:
command = f"cryptsetup luksUUID {self.luks_dev_path}" command = f'cryptsetup luksUUID {self.luks_dev_path}'
try: try:
return SysCommand(command).decode() return SysCommand(command).decode()
except SysCallError as err: except SysCallError as err:
info(f"Unable to get UUID for Luks device: {self.luks_dev_path}") info(f'Unable to get UUID for Luks device: {self.luks_dev_path}')
raise err raise err
def is_unlocked(self) -> bool: def is_unlocked(self) -> bool:
return self.mapper_name is not None and Path(f"/dev/mapper/{self.mapper_name}").exists() return self.mapper_name is not None and Path(f'/dev/mapper/{self.mapper_name}').exists()
def unlock(self, key_file: Path | None = None) -> None: def unlock(self, key_file: Path | None = None) -> None:
""" """
@ -136,29 +136,29 @@ class Luks2:
:param key_file: An alternative key file :param key_file: An alternative key file
:type key_file: Path :type key_file: Path
""" """
debug(f"Unlocking luks2 device: {self.luks_dev_path}") debug(f'Unlocking luks2 device: {self.luks_dev_path}')
if not self.mapper_name: if not self.mapper_name:
raise ValueError("mapper name missing") raise ValueError('mapper name missing')
key_file_arg, passphrase = self._get_passphrase_args(key_file) key_file_arg, passphrase = self._get_passphrase_args(key_file)
cmd = [ cmd = [
"cryptsetup", 'cryptsetup',
"open", 'open',
str(self.luks_dev_path), str(self.luks_dev_path),
str(self.mapper_name), str(self.mapper_name),
*key_file_arg, *key_file_arg,
"--type", '--type',
"luks2", 'luks2',
] ]
result = run(cmd, input_data=passphrase) result = run(cmd, input_data=passphrase)
debug(f"cryptsetup open output: {result.stdout.decode().rstrip()}") debug(f'cryptsetup open output: {result.stdout.decode().rstrip()}')
if not self.mapper_dev or not self.mapper_dev.is_symlink(): if not self.mapper_dev or not self.mapper_dev.is_symlink():
raise DiskError(f"Failed to open luks2 device: {self.luks_dev_path}") raise DiskError(f'Failed to open luks2 device: {self.luks_dev_path}')
def lock(self) -> None: def lock(self) -> None:
umount(self.luks_dev_path) umount(self.luks_dev_path)
@ -171,32 +171,32 @@ class Luks2:
for child in lsblk_info.children: for child in lsblk_info.children:
# Unmount the child location # Unmount the child location
for mountpoint in child.mountpoints: for mountpoint in child.mountpoints:
debug(f"Unmounting {mountpoint}") debug(f'Unmounting {mountpoint}')
umount(mountpoint, recursive=True) umount(mountpoint, recursive=True)
# And close it if possible. # And close it if possible.
debug(f"Closing crypt device {child.name}") debug(f'Closing crypt device {child.name}')
SysCommand(f"cryptsetup close {child.name}") SysCommand(f'cryptsetup close {child.name}')
def create_keyfile(self, target_path: Path, override: bool = False) -> None: def create_keyfile(self, target_path: Path, override: bool = False) -> None:
""" """
Routine to create keyfiles, so it can be moved elsewhere Routine to create keyfiles, so it can be moved elsewhere
""" """
if self.mapper_name is None: if self.mapper_name is None:
raise ValueError("Mapper name must be provided") raise ValueError('Mapper name must be provided')
# Once we store the key as ../xyzloop.key systemd-cryptsetup can # Once we store the key as ../xyzloop.key systemd-cryptsetup can
# automatically load this key if we name the device to "xyzloop" # automatically load this key if we name the device to "xyzloop"
kf_path = Path(f"/etc/cryptsetup-keys.d/{self.mapper_name}.key") kf_path = Path(f'/etc/cryptsetup-keys.d/{self.mapper_name}.key')
key_file = target_path / kf_path.relative_to(kf_path.root) key_file = target_path / kf_path.relative_to(kf_path.root)
crypttab_path = target_path / "etc/crypttab" crypttab_path = target_path / 'etc/crypttab'
if key_file.exists(): if key_file.exists():
if not override: if not override:
info(f"Key file {key_file} already exists, keeping existing") info(f'Key file {key_file} already exists, keeping existing')
return return
else: else:
info(f"Key file {key_file} already exists, overriding") info(f'Key file {key_file} already exists, overriding')
key_file.parent.mkdir(parents=True, exist_ok=True) key_file.parent.mkdir(parents=True, exist_ok=True)
@ -206,22 +206,22 @@ class Luks2:
key_file.chmod(0o400) key_file.chmod(0o400)
self._add_key(key_file) self._add_key(key_file)
self._crypttab(crypttab_path, kf_path, options=["luks", "key-slot=1"]) self._crypttab(crypttab_path, kf_path, options=['luks', 'key-slot=1'])
def _add_key(self, key_file: Path) -> None: def _add_key(self, key_file: Path) -> None:
debug(f"Adding additional key-file {key_file}") debug(f'Adding additional key-file {key_file}')
command = f"cryptsetup -q -v luksAddKey {self.luks_dev_path} {key_file}" command = f'cryptsetup -q -v luksAddKey {self.luks_dev_path} {key_file}'
worker = SysCommandWorker(command) worker = SysCommandWorker(command)
pw_injected = False pw_injected = False
while worker.is_alive(): while worker.is_alive():
if b"Enter any existing passphrase" in worker and pw_injected is False: if b'Enter any existing passphrase' in worker and pw_injected is False:
worker.write(self._password_bytes()) worker.write(self._password_bytes())
pw_injected = True pw_injected = True
if worker.exit_code != 0: if worker.exit_code != 0:
raise DiskError(f"Could not add encryption key {key_file} to {self.luks_dev_path}: {worker.decode()}") raise DiskError(f'Could not add encryption key {key_file} to {self.luks_dev_path}: {worker.decode()}')
def _crypttab( def _crypttab(
self, self,
@ -229,10 +229,10 @@ class Luks2:
key_file: Path, key_file: Path,
options: list[str], options: list[str],
) -> None: ) -> None:
debug(f"Adding crypttab entry for key {key_file}") debug(f'Adding crypttab entry for key {key_file}')
with open(crypttab_path, "a") as crypttab: with open(crypttab_path, 'a') as crypttab:
opt = ",".join(options) opt = ','.join(options)
uuid = self._get_luks_uuid() uuid = self._get_luks_uuid()
row = f"{self.mapper_name} UUID={uuid} {key_file} {opt}\n" row = f'{self.mapper_name} UUID={uuid} {key_file} {opt}\n'
crypttab.write(row) crypttab.write(row)

View File

@ -2,7 +2,7 @@ from .abstract_menu import AbstractMenu, AbstractSubMenu
from .list_manager import ListManager from .list_manager import ListManager
__all__ = [ __all__ = [
"AbstractMenu", 'AbstractMenu',
"AbstractSubMenu", 'AbstractSubMenu',
"ListManager", 'ListManager',
] ]

View File

@ -10,7 +10,7 @@ from archinstall.tui.types import Chars, FrameProperties, FrameStyle, PreviewSty
from ..output import error from ..output import error
CONFIG_KEY = "__config__" CONFIG_KEY = '__config__'
class AbstractMenu[ValueT]: class AbstractMenu[ValueT]:
@ -41,7 +41,7 @@ class AbstractMenu[ValueT]:
# TODO: skip processing when it comes from a planified exit # TODO: skip processing when it comes from a planified exit
if len(args) >= 2 and args[1]: if len(args) >= 2 and args[1]:
error(args[1]) error(args[1])
Tui.print("Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues") Tui.print('Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues')
raise args[1] raise args[1]
self.sync_all_to_config() self.sync_all_to_config()
@ -82,7 +82,7 @@ class AbstractMenu[ValueT]:
found = True found = True
if not found: if not found:
raise ValueError(f"No selector found: {key}") raise ValueError(f'No selector found: {key}')
def disable_all(self) -> None: def disable_all(self) -> None:
for item in self._menu_item_group.items: for item in self._menu_item_group.items:
@ -101,8 +101,8 @@ class AbstractMenu[ValueT]:
allow_reset=self._allow_reset, allow_reset=self._allow_reset,
reset_warning_msg=self._reset_warning, reset_warning_msg=self._reset_warning,
preview_style=PreviewStyle.RIGHT, preview_style=PreviewStyle.RIGHT,
preview_size="auto", preview_size='auto',
preview_frame=FrameProperties("Info", FrameStyle.MAX), preview_frame=FrameProperties('Info', FrameStyle.MAX),
).run() ).run()
match result.type_: match result.type_:
@ -128,7 +128,7 @@ class AbstractSubMenu[ValueT](AbstractMenu[ValueT]):
auto_cursor: bool = True, auto_cursor: bool = True,
allow_reset: bool = False, allow_reset: bool = False,
): ):
back_text = f"{Chars.Right_arrow} " + tr("Back") back_text = f'{Chars.Right_arrow} ' + tr('Back')
item_group.add_item(MenuItem(text=back_text)) item_group.add_item(MenuItem(text=back_text))
super().__init__( super().__init__(

View File

@ -36,9 +36,9 @@ class ListManager[ValueT]:
self._prompt = prompt self._prompt = prompt
self._separator = "" self._separator = ''
self._confirm_action = tr("Confirm and exit") self._confirm_action = tr('Confirm and exit')
self._cancel_action = tr("Cancel") self._cancel_action = tr('Cancel')
self._terminate_actions = [self._confirm_action, self._cancel_action] self._terminate_actions = [self._confirm_action, self._cancel_action]
self._base_actions = base_actions self._base_actions = base_actions
@ -66,7 +66,7 @@ class ListManager[ValueT]:
prompt = None prompt = None
if self._prompt is not None: if self._prompt is not None:
prompt = f"{self._prompt}\n\n" prompt = f'{self._prompt}\n\n'
prompt = None prompt = None
@ -82,7 +82,7 @@ class ListManager[ValueT]:
case ResultType.Selection: case ResultType.Selection:
value = result.get_value() value = result.get_value()
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
if value in self._base_actions: if value in self._base_actions:
value = cast(str, value) value = cast(str, value)
@ -108,7 +108,7 @@ class ListManager[ValueT]:
items = [MenuItem(o, value=o) for o in options] items = [MenuItem(o, value=o) for o in options]
group = MenuItemGroup(items, sort_items=False) group = MenuItemGroup(items, sort_items=False)
header = f"{self.selected_action_display(entry)}\n" header = f'{self.selected_action_display(entry)}\n'
result = SelectMenu[str]( result = SelectMenu[str](
group, group,
@ -122,7 +122,7 @@ class ListManager[ValueT]:
case ResultType.Selection: case ResultType.Selection:
value = result.get_value() value = result.get_value()
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
if value != self._cancel_action: if value != self._cancel_action:
self._data = self.handle_action(value, entry, self._data) self._data = self.handle_action(value, entry, self._data)
@ -132,14 +132,14 @@ class ListManager[ValueT]:
this will return the value to be displayed in the this will return the value to be displayed in the
"Select an action for '{}'" string "Select an action for '{}'" string
""" """
raise NotImplementedError("Please implement me in the child class") raise NotImplementedError('Please implement me in the child class')
def handle_action(self, action: str, entry: ValueT | None, data: list[ValueT]) -> list[ValueT]: def handle_action(self, action: str, entry: ValueT | None, data: list[ValueT]) -> list[ValueT]:
""" """
this function is called when a base action or this function is called when a base action or
a specific action for an entry is triggered a specific action for an entry is triggered
""" """
raise NotImplementedError("Please implement me in the child class") raise NotImplementedError('Please implement me in the child class')
def filter_options(self, selection: ValueT, options: list[str]) -> list[str]: def filter_options(self, selection: ValueT, options: list[str]) -> list[str]:
""" """

View File

@ -10,7 +10,7 @@ class MenuHelper:
data: list[Any], data: list[Any],
additional_options: list[str] = [], additional_options: list[str] = [],
) -> None: ) -> None:
self._separator = "" self._separator = ''
self._data = data self._data = data
self._additional_options = additional_options self._additional_options = additional_options
@ -39,10 +39,10 @@ class MenuHelper:
if data: if data:
table = FormattedOutput.as_table(data) table = FormattedOutput.as_table(data)
rows = table.split("\n") rows = table.split('\n')
# these are the header rows of the table # these are the header rows of the table
display_data = {f"{rows[0]}": None, f"{rows[1]}": None} display_data = {f'{rows[0]}': None, f'{rows[1]}': None}
for row, entry in zip(rows[2:], data): for row, entry in zip(rows[2:], data):
display_data[row] = entry display_data[row] = entry

View File

@ -29,16 +29,16 @@ from .output import FormattedOutput, debug
class CustomMirrorRepositoriesList(ListManager[CustomRepository]): class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
def __init__(self, custom_repositories: list[CustomRepository]): def __init__(self, custom_repositories: list[CustomRepository]):
self._actions = [ self._actions = [
tr("Add a custom repository"), tr('Add a custom repository'),
tr("Change custom repository"), tr('Change custom repository'),
tr("Delete custom repository"), tr('Delete custom repository'),
] ]
super().__init__( super().__init__(
custom_repositories, custom_repositories,
[self._actions[0]], [self._actions[0]],
self._actions[1:], self._actions[1:],
"", '',
) )
@override @override
@ -69,7 +69,7 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
def _add_custom_repository(self, preset: CustomRepository | None = None) -> CustomRepository | None: def _add_custom_repository(self, preset: CustomRepository | None = None) -> CustomRepository | None:
edit_result = EditMenu( edit_result = EditMenu(
tr("Repository name"), tr('Repository name'),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
allow_skip=True, allow_skip=True,
default_text=preset.name if preset else None, default_text=preset.name if preset else None,
@ -81,12 +81,12 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
case ResultType.Skip: case ResultType.Skip:
return preset return preset
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
header = f"{tr('Name')}: {name}" header = f'{tr("Name")}: {name}'
edit_result = EditMenu( edit_result = EditMenu(
tr("Url"), tr('Url'),
header=header, header=header,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
allow_skip=True, allow_skip=True,
@ -99,10 +99,10 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
case ResultType.Skip: case ResultType.Skip:
return preset return preset
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
header += f"\n{tr('Url')}: {url}\n" header += f'\n{tr("Url")}: {url}\n'
prompt = f"{header}\n" + tr("Select signature check") prompt = f'{header}\n' + tr('Select signature check')
sign_chk_items = [MenuItem(s.value, value=s.value) for s in SignCheck] sign_chk_items = [MenuItem(s.value, value=s.value) for s in SignCheck]
group = MenuItemGroup(sign_chk_items, sort_items=False) group = MenuItemGroup(sign_chk_items, sort_items=False)
@ -121,10 +121,10 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
case ResultType.Selection: case ResultType.Selection:
sign_check = SignCheck(result.get_value()) sign_check = SignCheck(result.get_value())
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
header += f"{tr('Signature check')}: {sign_check.value}\n" header += f'{tr("Signature check")}: {sign_check.value}\n'
prompt = f"{header}\n" + "Select signature option" prompt = f'{header}\n' + 'Select signature option'
sign_opt_items = [MenuItem(s.value, value=s.value) for s in SignOption] sign_opt_items = [MenuItem(s.value, value=s.value) for s in SignOption]
group = MenuItemGroup(sign_opt_items, sort_items=False) group = MenuItemGroup(sign_opt_items, sort_items=False)
@ -143,7 +143,7 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
case ResultType.Selection: case ResultType.Selection:
sign_opt = SignOption(result.get_value()) sign_opt = SignOption(result.get_value())
case _: case _:
raise ValueError("Unhandled return type") raise ValueError('Unhandled return type')
return CustomRepository(name, url, sign_check, sign_opt) return CustomRepository(name, url, sign_check, sign_opt)
@ -151,16 +151,16 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
class CustomMirrorServersList(ListManager[CustomServer]): class CustomMirrorServersList(ListManager[CustomServer]):
def __init__(self, custom_servers: list[CustomServer]): def __init__(self, custom_servers: list[CustomServer]):
self._actions = [ self._actions = [
tr("Add a custom server"), tr('Add a custom server'),
tr("Change custom server"), tr('Change custom server'),
tr("Delete custom server"), tr('Delete custom server'),
] ]
super().__init__( super().__init__(
custom_servers, custom_servers,
[self._actions[0]], [self._actions[0]],
self._actions[1:], self._actions[1:],
"", '',
) )
@override @override
@ -191,7 +191,7 @@ class CustomMirrorServersList(ListManager[CustomServer]):
def _add_custom_server(self, preset: CustomServer | None = None) -> CustomServer | None: def _add_custom_server(self, preset: CustomServer | None = None) -> CustomServer | None:
edit_result = EditMenu( edit_result = EditMenu(
tr("Server url"), tr('Server url'),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
allow_skip=True, allow_skip=True,
default_text=preset.url if preset else None, default_text=preset.url if preset else None,
@ -229,54 +229,54 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
def _define_menu_options(self) -> list[MenuItem]: def _define_menu_options(self) -> list[MenuItem]:
return [ return [
MenuItem( MenuItem(
text=tr("Select regions"), text=tr('Select regions'),
action=select_mirror_regions, action=select_mirror_regions,
value=self._mirror_config.mirror_regions, value=self._mirror_config.mirror_regions,
preview_action=self._prev_regions, preview_action=self._prev_regions,
key="mirror_regions", key='mirror_regions',
), ),
MenuItem( MenuItem(
text=tr("Add custom servers"), text=tr('Add custom servers'),
action=add_custom_mirror_servers, action=add_custom_mirror_servers,
value=self._mirror_config.custom_servers, value=self._mirror_config.custom_servers,
preview_action=self._prev_custom_servers, preview_action=self._prev_custom_servers,
key="custom_servers", key='custom_servers',
), ),
MenuItem( MenuItem(
text=tr("Optional repositories"), text=tr('Optional repositories'),
action=select_optional_repositories, action=select_optional_repositories,
value=[], value=[],
preview_action=self._prev_additional_repos, preview_action=self._prev_additional_repos,
key="optional_repositories", key='optional_repositories',
), ),
MenuItem( MenuItem(
text=tr("Add custom repository"), text=tr('Add custom repository'),
action=select_custom_mirror, action=select_custom_mirror,
value=self._mirror_config.custom_repositories, value=self._mirror_config.custom_repositories,
preview_action=self._prev_custom_mirror, preview_action=self._prev_custom_mirror,
key="custom_repositories", key='custom_repositories',
), ),
] ]
def _prev_regions(self, item: MenuItem) -> str | None: def _prev_regions(self, item: MenuItem) -> str | None:
regions = item.get_value() regions = item.get_value()
output = "" output = ''
for region in regions: for region in regions:
output += f"{region.name}\n" output += f'{region.name}\n'
for url in region.urls: for url in region.urls:
output += f" - {url}\n" output += f' - {url}\n'
output += "\n" output += '\n'
return output return output
def _prev_additional_repos(self, item: MenuItem) -> str | None: def _prev_additional_repos(self, item: MenuItem) -> str | None:
if item.value: if item.value:
repositories: list[Repository] = item.value repositories: list[Repository] = item.value
repos = ", ".join([repo.value for repo in repositories]) repos = ', '.join([repo.value for repo in repositories])
return f"{tr('Additional repositories')}: {repos}" return f'{tr("Additional repositories")}: {repos}'
return None return None
def _prev_custom_mirror(self, item: MenuItem) -> str | None: def _prev_custom_mirror(self, item: MenuItem) -> str | None:
@ -292,7 +292,7 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
return None return None
custom_servers: list[CustomServer] = item.value custom_servers: list[CustomServer] = item.value
output = "\n".join([server.url for server in custom_servers]) output = '\n'.join([server.url for server in custom_servers])
return output.strip() return output.strip()
@override @override
@ -302,7 +302,7 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]: def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]:
Tui.print(tr("Loading mirror regions..."), clear_screen=True) Tui.print(tr('Loading mirror regions...'), clear_screen=True)
mirror_list_handler.load_mirrors() mirror_list_handler.load_mirrors()
available_regions = mirror_list_handler.get_mirror_regions() available_regions = mirror_list_handler.get_mirror_regions()
@ -320,7 +320,7 @@ def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]:
result = SelectMenu[MirrorRegion]( result = SelectMenu[MirrorRegion](
group, group,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Mirror regions")), frame=FrameProperties.min(tr('Mirror regions')),
allow_reset=True, allow_reset=True,
allow_skip=True, allow_skip=True,
multi=True, multi=True,
@ -362,7 +362,7 @@ def select_optional_repositories(preset: list[Repository]) -> list[Repository]:
result = SelectMenu[Repository]( result = SelectMenu[Repository](
group, group,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min("Additional repositories"), frame=FrameProperties.min('Additional repositories'),
allow_reset=True, allow_reset=True,
allow_skip=True, allow_skip=True,
multi=True, multi=True,
@ -380,7 +380,7 @@ def select_optional_repositories(preset: list[Repository]) -> list[Repository]:
class MirrorListHandler: class MirrorListHandler:
def __init__( def __init__(
self, self,
local_mirrorlist: Path = Path("/etc/pacman.d/mirrorlist"), local_mirrorlist: Path = Path('/etc/pacman.d/mirrorlist'),
) -> None: ) -> None:
self._local_mirrorlist = local_mirrorlist self._local_mirrorlist = local_mirrorlist
self._status_mappings: dict[str, list[MirrorStatusEntryV3]] | None = None self._status_mappings: dict[str, list[MirrorStatusEntryV3]] | None = None
@ -413,7 +413,7 @@ class MirrorListHandler:
self.load_local_mirrors() self.load_local_mirrors()
def load_remote_mirrors(self) -> bool: def load_remote_mirrors(self) -> bool:
url = "https://archlinux.org/mirrors/status/json/" url = 'https://archlinux.org/mirrors/status/json/'
attempts = 3 attempts = 3
for attempt_nr in range(attempts): for attempt_nr in range(attempts):
@ -422,14 +422,14 @@ class MirrorListHandler:
self._status_mappings = self._parse_remote_mirror_list(mirrorlist) self._status_mappings = self._parse_remote_mirror_list(mirrorlist)
return True return True
except Exception as e: except Exception as e:
debug(f"Error while fetching mirror list: {e}") debug(f'Error while fetching mirror list: {e}')
time.sleep(attempt_nr + 1) time.sleep(attempt_nr + 1)
debug("Unable to fetch mirror list remotely, falling back to local mirror list") debug('Unable to fetch mirror list remotely, falling back to local mirror list')
return False return False
def load_local_mirrors(self) -> None: def load_local_mirrors(self) -> None:
with self._local_mirrorlist.open("r") as fp: with self._local_mirrorlist.open('r') as fp:
mirrorlist = fp.read() mirrorlist = fp.read()
self._status_mappings = self._parse_locale_mirrors(mirrorlist) self._status_mappings = self._parse_locale_mirrors(mirrorlist)
@ -456,14 +456,14 @@ class MirrorListHandler:
): ):
continue continue
if mirror.country == "": if mirror.country == '':
# TODO: This should be removed once RFC!29 is merged and completed # TODO: This should be removed once RFC!29 is merged and completed
# Until then, there are mirrors which lacks data in the backend # Until then, there are mirrors which lacks data in the backend
# and there is no way of knowing where they're located. # and there is no way of knowing where they're located.
# So we have to assume world-wide # So we have to assume world-wide
mirror.country = "Worldwide" mirror.country = 'Worldwide'
if mirror.url.startswith("http"): if mirror.url.startswith('http'):
sorting_placeholder.setdefault(mirror.country, []).append(mirror) sorting_placeholder.setdefault(mirror.country, []).append(mirror)
sorted_by_regions: dict[str, list[MirrorStatusEntryV3]] = dict( sorted_by_regions: dict[str, list[MirrorStatusEntryV3]] = dict(
@ -480,35 +480,35 @@ class MirrorListHandler:
mirror_list: dict[str, list[MirrorStatusEntryV3]] = {} mirror_list: dict[str, list[MirrorStatusEntryV3]] = {}
current_region = "" current_region = ''
for line in lines: for line in lines:
line = line.strip() line = line.strip()
if line.startswith("## "): if line.startswith('## '):
current_region = line.replace("## ", "").strip() current_region = line.replace('## ', '').strip()
mirror_list.setdefault(current_region, []) mirror_list.setdefault(current_region, [])
if line.startswith("Server = "): if line.startswith('Server = '):
if not current_region: if not current_region:
current_region = "Local" current_region = 'Local'
mirror_list.setdefault(current_region, []) mirror_list.setdefault(current_region, [])
url = line.removeprefix("Server = ") url = line.removeprefix('Server = ')
mirror_entry = MirrorStatusEntryV3( mirror_entry = MirrorStatusEntryV3(
url=url.removesuffix("$repo/os/$arch"), url=url.removesuffix('$repo/os/$arch'),
protocol=urllib.parse.urlparse(url).scheme, protocol=urllib.parse.urlparse(url).scheme,
active=True, active=True,
country=current_region or "Worldwide", country=current_region or 'Worldwide',
# The following values are normally populated by # The following values are normally populated by
# archlinux.org mirror-list endpoint, and can't be known # archlinux.org mirror-list endpoint, and can't be known
# from just the local mirror-list file. # from just the local mirror-list file.
country_code="WW", country_code='WW',
isos=True, isos=True,
ipv4=True, ipv4=True,
ipv6=True, ipv6=True,
details="Locally defined mirror", details='Locally defined mirror',
) )
mirror_list[current_region].append(mirror_entry) mirror_list[current_region].append(mirror_entry)

View File

@ -35,46 +35,46 @@ from .profile_model import ProfileConfiguration
from .users import PasswordStrength, User from .users import PasswordStrength, User
__all__ = [ __all__ = [
"Audio", 'Audio',
"AudioConfiguration", 'AudioConfiguration',
"BDevice", 'BDevice',
"Bootloader", 'Bootloader',
"CustomRepository", 'CustomRepository',
"DeviceGeometry", 'DeviceGeometry',
"DeviceModification", 'DeviceModification',
"DiskEncryption", 'DiskEncryption',
"DiskLayoutConfiguration", 'DiskLayoutConfiguration',
"DiskLayoutType", 'DiskLayoutType',
"EncryptionType", 'EncryptionType',
"Fido2Device", 'Fido2Device',
"FilesystemType", 'FilesystemType',
"LocalPackage", 'LocalPackage',
"LocaleConfiguration", 'LocaleConfiguration',
"LsblkInfo", 'LsblkInfo',
"LvmConfiguration", 'LvmConfiguration',
"LvmLayoutType", 'LvmLayoutType',
"LvmVolume", 'LvmVolume',
"LvmVolumeGroup", 'LvmVolumeGroup',
"LvmVolumeStatus", 'LvmVolumeStatus',
"MirrorConfiguration", 'MirrorConfiguration',
"MirrorRegion", 'MirrorRegion',
"ModificationStatus", 'ModificationStatus',
"NetworkConfiguration", 'NetworkConfiguration',
"Nic", 'Nic',
"NicType", 'NicType',
"PackageSearch", 'PackageSearch',
"PackageSearchResult", 'PackageSearchResult',
"PartitionFlag", 'PartitionFlag',
"PartitionModification", 'PartitionModification',
"PartitionTable", 'PartitionTable',
"PartitionType", 'PartitionType',
"PasswordStrength", 'PasswordStrength',
"ProfileConfiguration", 'ProfileConfiguration',
"Repository", 'Repository',
"SectorSize", 'SectorSize',
"Size", 'Size',
"SubvolumeModification", 'SubvolumeModification',
"Unit", 'Unit',
"User", 'User',
"_DeviceInfo", '_DeviceInfo',
] ]

View File

@ -10,7 +10,7 @@ if TYPE_CHECKING:
class Audio(StrEnum): class Audio(StrEnum):
NO_AUDIO = "No audio server" NO_AUDIO = 'No audio server'
PIPEWIRE = auto() PIPEWIRE = auto()
PULSEAUDIO = auto() PULSEAUDIO = auto()
@ -21,20 +21,20 @@ class AudioConfiguration:
def json(self) -> dict[str, str]: def json(self) -> dict[str, str]:
return { return {
"audio": self.audio.value, 'audio': self.audio.value,
} }
@staticmethod @staticmethod
def parse_arg(arg: dict[str, str]) -> "AudioConfiguration": def parse_arg(arg: dict[str, str]) -> 'AudioConfiguration':
return AudioConfiguration( return AudioConfiguration(
Audio(arg["audio"]), Audio(arg['audio']),
) )
def install_audio_config( def install_audio_config(
self, self,
installation: "Installer", installation: 'Installer',
) -> None: ) -> None:
info(f"Installing audio server: {self.audio.name}") info(f'Installing audio server: {self.audio.name}')
from ...default_profiles.applications.pipewire import PipewireProfile from ...default_profiles.applications.pipewire import PipewireProfile
@ -42,11 +42,11 @@ class AudioConfiguration:
case Audio.PIPEWIRE: case Audio.PIPEWIRE:
PipewireProfile().install(installation) PipewireProfile().install(installation)
case Audio.PULSEAUDIO: case Audio.PULSEAUDIO:
installation.add_additional_packages("pulseaudio") installation.add_additional_packages('pulseaudio')
if self.audio != Audio.NO_AUDIO: if self.audio != Audio.NO_AUDIO:
if SysInfo.requires_sof_fw(): if SysInfo.requires_sof_fw():
installation.add_additional_packages("sof-firmware") installation.add_additional_packages('sof-firmware')
if SysInfo.requires_alsa_fw(): if SysInfo.requires_alsa_fw():
installation.add_additional_packages("alsa-firmware") installation.add_additional_packages('alsa-firmware')

View File

@ -8,10 +8,10 @@ from ..output import warn
class Bootloader(Enum): class Bootloader(Enum):
Systemd = "Systemd-boot" Systemd = 'Systemd-boot'
Grub = "Grub" Grub = 'Grub'
Efistub = "Efistub" Efistub = 'Efistub'
Limine = "Limine" Limine = 'Limine'
def has_uki_support(self) -> bool: def has_uki_support(self) -> bool:
match self: match self:
@ -40,7 +40,7 @@ class Bootloader(Enum):
bootloader = bootloader.capitalize() bootloader = bootloader.capitalize()
if bootloader not in cls.values(): if bootloader not in cls.values():
values = ", ".join(cls.values()) values = ', '.join(cls.values())
warn(f'Invalid bootloader value "{bootloader}". Allowed values: {values}') warn(f'Invalid bootloader value "{bootloader}". Allowed values: {values}')
sys.exit(1) sys.exit(1)
return Bootloader(bootloader) return Bootloader(bootloader)

File diff suppressed because it is too large Load Diff

View File

@ -13,42 +13,42 @@ class LocaleConfiguration:
sys_enc: str sys_enc: str
@staticmethod @staticmethod
def default() -> "LocaleConfiguration": def default() -> 'LocaleConfiguration':
layout = get_kb_layout() layout = get_kb_layout()
if layout == "": if layout == '':
layout = "us" layout = 'us'
return LocaleConfiguration(layout, "en_US.UTF-8", "UTF-8") return LocaleConfiguration(layout, 'en_US.UTF-8', 'UTF-8')
def json(self) -> dict[str, str]: def json(self) -> dict[str, str]:
return { return {
"kb_layout": self.kb_layout, 'kb_layout': self.kb_layout,
"sys_lang": self.sys_lang, 'sys_lang': self.sys_lang,
"sys_enc": self.sys_enc, 'sys_enc': self.sys_enc,
} }
def preview(self) -> str: def preview(self) -> str:
output = "{}: {}\n".format(tr("Keyboard layout"), self.kb_layout) output = '{}: {}\n'.format(tr('Keyboard layout'), self.kb_layout)
output += "{}: {}\n".format(tr("Locale language"), self.sys_lang) output += '{}: {}\n'.format(tr('Locale language'), self.sys_lang)
output += "{}: {}".format(tr("Locale encoding"), self.sys_enc) output += '{}: {}'.format(tr('Locale encoding'), self.sys_enc)
return output return output
@classmethod @classmethod
def _load_config(cls, config: "LocaleConfiguration", args: dict[str, str]) -> "LocaleConfiguration": def _load_config(cls, config: 'LocaleConfiguration', args: dict[str, str]) -> 'LocaleConfiguration':
if "sys_lang" in args: if 'sys_lang' in args:
config.sys_lang = args["sys_lang"] config.sys_lang = args['sys_lang']
if "sys_enc" in args: if 'sys_enc' in args:
config.sys_enc = args["sys_enc"] config.sys_enc = args['sys_enc']
if "kb_layout" in args: if 'kb_layout' in args:
config.kb_layout = args["kb_layout"] config.kb_layout = args['kb_layout']
return config return config
@classmethod @classmethod
def parse_arg(cls, args: dict[str, Any]) -> "LocaleConfiguration": def parse_arg(cls, args: dict[str, Any]) -> 'LocaleConfiguration':
default = cls.default() default = cls.default()
if "locale_config" in args: if 'locale_config' in args:
default = cls._load_config(default, args["locale_config"]) default = cls._load_config(default, args['locale_config'])
else: else:
default = cls._load_config(default, args) default = cls._load_config(default, args)

View File

@ -38,7 +38,7 @@ class MirrorStatusEntryV3(BaseModel):
@property @property
def server_url(self) -> str: def server_url(self) -> str:
return f"{self.url}$repo/os/$arch" return f'{self.url}$repo/os/$arch'
@property @property
def speed(self) -> float: def speed(self) -> float:
@ -50,8 +50,8 @@ class MirrorStatusEntryV3(BaseModel):
retry = 0 retry = 0
while retry < self._speedtest_retries and self._speed is None: while retry < self._speedtest_retries and self._speed is None:
debug(f"Checking download speed of {self._hostname}[{self.score}] by fetching: {self.url}core/os/x86_64/core.db") debug(f'Checking download speed of {self._hostname}[{self.score}] by fetching: {self.url}core/os/x86_64/core.db')
req = urllib.request.Request(url=f"{self.url}core/os/x86_64/core.db") req = urllib.request.Request(url=f'{self.url}core/os/x86_64/core.db')
try: try:
with urllib.request.urlopen(req, None, 5) as handle, DownloadTimer(timeout=5) as timer: with urllib.request.urlopen(req, None, 5) as handle, DownloadTimer(timeout=5) as timer:
@ -59,17 +59,17 @@ class MirrorStatusEntryV3(BaseModel):
assert timer.time is not None assert timer.time is not None
self._speed = size / timer.time self._speed = size / timer.time
debug(f" speed: {self._speed} ({int(self._speed / 1024 / 1024 * 100) / 100}MiB/s)") debug(f' speed: {self._speed} ({int(self._speed / 1024 / 1024 * 100) / 100}MiB/s)')
# Do not retry error # Do not retry error
except urllib.error.URLError as error: except urllib.error.URLError as error:
debug(f" speed: <undetermined> ({error}), skip") debug(f' speed: <undetermined> ({error}), skip')
self._speed = 0 self._speed = 0
# Do retry error # Do retry error
except (http.client.IncompleteRead, ConnectionResetError) as error: except (http.client.IncompleteRead, ConnectionResetError) as error:
debug(f" speed: <undetermined> ({error}), retry") debug(f' speed: <undetermined> ({error}), retry')
# Catch all # Catch all
except Exception as error: except Exception as error:
debug(f" speed: <undetermined> ({error}), skip") debug(f' speed: <undetermined> ({error}), skip')
self._speed = 0 self._speed = 0
retry += 1 retry += 1
@ -87,27 +87,27 @@ class MirrorStatusEntryV3(BaseModel):
We do this because some hosts blocks ICMP so we'll have to rely on .speed() instead which is slower. We do this because some hosts blocks ICMP so we'll have to rely on .speed() instead which is slower.
""" """
if self._latency is None: if self._latency is None:
debug(f"Checking latency for {self.url}") debug(f'Checking latency for {self.url}')
self._latency = ping(self._hostname, timeout=2) self._latency = ping(self._hostname, timeout=2)
debug(f" latency: {self._latency}") debug(f' latency: {self._latency}')
return self._latency return self._latency
@classmethod @classmethod
@field_validator("score", mode="before") @field_validator('score', mode='before')
def validate_score(cls, value: float) -> int | None: def validate_score(cls, value: float) -> int | None:
if value is not None: if value is not None:
value = round(value) value = round(value)
debug(f" score: {value}") debug(f' score: {value}')
return value return value
@model_validator(mode="after") @model_validator(mode='after')
def debug_output(self, validation_info) -> "MirrorStatusEntryV3": def debug_output(self, validation_info) -> 'MirrorStatusEntryV3':
self._hostname, *port = urllib.parse.urlparse(self.url).netloc.split(":", 1) self._hostname, *port = urllib.parse.urlparse(self.url).netloc.split(':', 1)
self._port = int(port[0]) if port and len(port) >= 1 else None self._port = int(port[0]) if port and len(port) >= 1 else None
debug(f"Loaded mirror {self._hostname}" + (f" with current score of {self.score}" if self.score else "")) debug(f'Loaded mirror {self._hostname}' + (f' with current score of {self.score}' if self.score else ''))
return self return self
@ -118,16 +118,16 @@ class MirrorStatusListV3(BaseModel):
urls: list[MirrorStatusEntryV3] urls: list[MirrorStatusEntryV3]
version: int version: int
@model_validator(mode="before") @model_validator(mode='before')
@classmethod @classmethod
def check_model( def check_model(
cls, cls,
data: dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]], data: dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]],
) -> dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]]: ) -> dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]]:
if data.get("version") == 3: if data.get('version') == 3:
return data return data
raise ValueError("MirrorStatusListV3 only accepts version 3 data from https://archlinux.org/mirrors/status/json/") raise ValueError('MirrorStatusListV3 only accepts version 3 data from https://archlinux.org/mirrors/status/json/')
@dataclass @dataclass
@ -146,14 +146,14 @@ class MirrorRegion:
class SignCheck(Enum): class SignCheck(Enum):
Never = "Never" Never = 'Never'
Optional = "Optional" Optional = 'Optional'
Required = "Required" Required = 'Required'
class SignOption(Enum): class SignOption(Enum):
TrustedOnly = "TrustedOnly" TrustedOnly = 'TrustedOnly'
TrustAll = "TrustAll" TrustAll = 'TrustAll'
class _CustomRepositorySerialization(TypedDict): class _CustomRepositorySerialization(TypedDict):
@ -172,30 +172,30 @@ class CustomRepository:
def table_data(self) -> dict[str, str]: def table_data(self) -> dict[str, str]:
return { return {
"Name": self.name, 'Name': self.name,
"Url": self.url, 'Url': self.url,
"Sign check": self.sign_check.value, 'Sign check': self.sign_check.value,
"Sign options": self.sign_option.value, 'Sign options': self.sign_option.value,
} }
def json(self) -> _CustomRepositorySerialization: def json(self) -> _CustomRepositorySerialization:
return { return {
"name": self.name, 'name': self.name,
"url": self.url, 'url': self.url,
"sign_check": self.sign_check.value, 'sign_check': self.sign_check.value,
"sign_option": self.sign_option.value, 'sign_option': self.sign_option.value,
} }
@classmethod @classmethod
def parse_args(cls, args: list[dict[str, str]]) -> list["CustomRepository"]: def parse_args(cls, args: list[dict[str, str]]) -> list['CustomRepository']:
configs = [] configs = []
for arg in args: for arg in args:
configs.append( configs.append(
CustomRepository( CustomRepository(
arg["name"], arg['name'],
arg["url"], arg['url'],
SignCheck(arg["sign_check"]), SignCheck(arg['sign_check']),
SignOption(arg["sign_option"]), SignOption(arg['sign_option']),
), ),
) )
@ -207,17 +207,17 @@ class CustomServer:
url: str url: str
def table_data(self) -> dict[str, str]: def table_data(self) -> dict[str, str]:
return {"Url": self.url} return {'Url': self.url}
def json(self) -> dict[str, str]: def json(self) -> dict[str, str]:
return {"url": self.url} return {'url': self.url}
@classmethod @classmethod
def parse_args(cls, args: list[dict[str, str]]) -> list["CustomServer"]: def parse_args(cls, args: list[dict[str, str]]) -> list['CustomServer']:
configs = [] configs = []
for arg in args: for arg in args:
configs.append( configs.append(
CustomServer(arg["url"]), CustomServer(arg['url']),
) )
return configs return configs
@ -239,11 +239,11 @@ class MirrorConfiguration:
@property @property
def region_names(self) -> str: def region_names(self) -> str:
return "\n".join([m.name for m in self.mirror_regions]) return '\n'.join([m.name for m in self.mirror_regions])
@property @property
def custom_server_urls(self) -> str: def custom_server_urls(self) -> str:
return "\n".join([s.url for s in self.custom_servers]) return '\n'.join([s.url for s in self.custom_servers])
def json(self) -> _MirrorConfigurationSerialization: def json(self) -> _MirrorConfigurationSerialization:
regions = {} regions = {}
@ -251,26 +251,26 @@ class MirrorConfiguration:
regions.update(m.json()) regions.update(m.json())
return { return {
"mirror_regions": regions, 'mirror_regions': regions,
"custom_servers": self.custom_servers, 'custom_servers': self.custom_servers,
"optional_repositories": [r.value for r in self.optional_repositories], 'optional_repositories': [r.value for r in self.optional_repositories],
"custom_repositories": [c.json() for c in self.custom_repositories], 'custom_repositories': [c.json() for c in self.custom_repositories],
} }
def custom_servers_config(self) -> str: def custom_servers_config(self) -> str:
config = "" config = ''
if self.custom_servers: if self.custom_servers:
config += "## Custom Servers\n" config += '## Custom Servers\n'
for server in self.custom_servers: for server in self.custom_servers:
config += f"Server = {server.url}\n" config += f'Server = {server.url}\n'
return config.strip() return config.strip()
def regions_config(self, speed_sort: bool = True) -> str: def regions_config(self, speed_sort: bool = True) -> str:
from ..mirrors import mirror_list_handler from ..mirrors import mirror_list_handler
config = "" config = ''
for mirror_region in self.mirror_regions: for mirror_region in self.mirror_regions:
sorted_stati = mirror_list_handler.get_status_by_region( sorted_stati = mirror_list_handler.get_status_by_region(
@ -278,20 +278,20 @@ class MirrorConfiguration:
speed_sort=speed_sort, speed_sort=speed_sort,
) )
config += f"\n\n## {mirror_region.name}\n" config += f'\n\n## {mirror_region.name}\n'
for status in sorted_stati: for status in sorted_stati:
config += f"Server = {status.server_url}\n" config += f'Server = {status.server_url}\n'
return config return config
def repositories_config(self) -> str: def repositories_config(self) -> str:
config = "" config = ''
for repo in self.custom_repositories: for repo in self.custom_repositories:
config += f"\n\n[{repo.name}]\n" config += f'\n\n[{repo.name}]\n'
config += f"SigLevel = {repo.sign_check.value} {repo.sign_option.value}\n" config += f'SigLevel = {repo.sign_check.value} {repo.sign_option.value}\n'
config += f"Server = {repo.url}\n" config += f'Server = {repo.url}\n'
return config return config
@ -300,25 +300,25 @@ class MirrorConfiguration:
cls, cls,
args: dict[str, Any], args: dict[str, Any],
backwards_compatible_repo: list[Repository] = [], backwards_compatible_repo: list[Repository] = [],
) -> "MirrorConfiguration": ) -> 'MirrorConfiguration':
config = MirrorConfiguration() config = MirrorConfiguration()
mirror_regions = args.get("mirror_regions", []) mirror_regions = args.get('mirror_regions', [])
if mirror_regions: if mirror_regions:
for region, urls in mirror_regions.items(): for region, urls in mirror_regions.items():
config.mirror_regions.append(MirrorRegion(region, urls)) config.mirror_regions.append(MirrorRegion(region, urls))
if args.get("custom_servers"): if args.get('custom_servers'):
config.custom_servers = CustomServer.parse_args(args["custom_servers"]) config.custom_servers = CustomServer.parse_args(args['custom_servers'])
# backwards compatibility with the new custom_repository # backwards compatibility with the new custom_repository
if "custom_mirrors" in args: if 'custom_mirrors' in args:
config.custom_repositories = CustomRepository.parse_args(args["custom_mirrors"]) config.custom_repositories = CustomRepository.parse_args(args['custom_mirrors'])
if "custom_repositories" in args: if 'custom_repositories' in args:
config.custom_repositories = CustomRepository.parse_args(args["custom_repositories"]) config.custom_repositories = CustomRepository.parse_args(args['custom_repositories'])
if "optional_repositories" in args: if 'optional_repositories' in args:
config.optional_repositories = [Repository(r) for r in args["optional_repositories"]] config.optional_repositories = [Repository(r) for r in args['optional_repositories']]
if backwards_compatible_repo: if backwards_compatible_repo:
for r in backwards_compatible_repo: for r in backwards_compatible_repo:

View File

@ -13,18 +13,18 @@ if TYPE_CHECKING:
class NicType(Enum): class NicType(Enum):
ISO = "iso" ISO = 'iso'
NM = "nm" NM = 'nm'
MANUAL = "manual" MANUAL = 'manual'
def display_msg(self) -> str: def display_msg(self) -> str:
match self: match self:
case NicType.ISO: case NicType.ISO:
return tr("Copy ISO network configuration to installation") return tr('Copy ISO network configuration to installation')
case NicType.NM: case NicType.NM:
return tr("Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)") return tr('Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)')
case NicType.MANUAL: case NicType.MANUAL:
return tr("Manual configuration") return tr('Manual configuration')
class _NicSerialization(TypedDict): class _NicSerialization(TypedDict):
@ -45,30 +45,30 @@ class Nic:
def table_data(self) -> dict[str, str | bool | list[str]]: def table_data(self) -> dict[str, str | bool | list[str]]:
return { return {
"iface": self.iface if self.iface else "", 'iface': self.iface if self.iface else '',
"ip": self.ip if self.ip else "", 'ip': self.ip if self.ip else '',
"dhcp": self.dhcp, 'dhcp': self.dhcp,
"gateway": self.gateway if self.gateway else "", 'gateway': self.gateway if self.gateway else '',
"dns": self.dns, 'dns': self.dns,
} }
def json(self) -> _NicSerialization: def json(self) -> _NicSerialization:
return { return {
"iface": self.iface, 'iface': self.iface,
"ip": self.ip, 'ip': self.ip,
"dhcp": self.dhcp, 'dhcp': self.dhcp,
"gateway": self.gateway, 'gateway': self.gateway,
"dns": self.dns, 'dns': self.dns,
} }
@staticmethod @staticmethod
def parse_arg(arg: _NicSerialization) -> Nic: def parse_arg(arg: _NicSerialization) -> Nic:
return Nic( return Nic(
iface=arg.get("iface", None), iface=arg.get('iface', None),
ip=arg.get("ip", None), ip=arg.get('ip', None),
dhcp=arg.get("dhcp", True), dhcp=arg.get('dhcp', True),
gateway=arg.get("gateway", None), gateway=arg.get('gateway', None),
dns=arg.get("dns", []), dns=arg.get('dns', []),
) )
def as_systemd_config(self) -> str: def as_systemd_config(self) -> str:
@ -76,25 +76,25 @@ class Nic:
network: list[tuple[str, str]] = [] network: list[tuple[str, str]] = []
if self.iface: if self.iface:
match.append(("Name", self.iface)) match.append(('Name', self.iface))
if self.dhcp: if self.dhcp:
network.append(("DHCP", "yes")) network.append(('DHCP', 'yes'))
else: else:
if self.ip: if self.ip:
network.append(("Address", self.ip)) network.append(('Address', self.ip))
if self.gateway: if self.gateway:
network.append(("Gateway", self.gateway)) network.append(('Gateway', self.gateway))
for dns in self.dns: for dns in self.dns:
network.append(("DNS", dns)) network.append(('DNS', dns))
config = {"Match": match, "Network": network} config = {'Match': match, 'Network': network}
config_str = "" config_str = ''
for top, entries in config.items(): for top, entries in config.items():
config_str += f"[{top}]\n" config_str += f'[{top}]\n'
config_str += "\n".join([f"{k}={v}" for k, v in entries]) config_str += '\n'.join([f'{k}={v}' for k, v in entries])
config_str += "\n\n" config_str += '\n\n'
return config_str return config_str
@ -110,15 +110,15 @@ class NetworkConfiguration:
nics: list[Nic] = field(default_factory=list) nics: list[Nic] = field(default_factory=list)
def json(self) -> _NetworkConfigurationSerialization: def json(self) -> _NetworkConfigurationSerialization:
config: _NetworkConfigurationSerialization = {"type": self.type.value} config: _NetworkConfigurationSerialization = {'type': self.type.value}
if self.nics: if self.nics:
config["nics"] = [n.json() for n in self.nics] config['nics'] = [n.json() for n in self.nics]
return config return config
@staticmethod @staticmethod
def parse_arg(config: _NetworkConfigurationSerialization) -> NetworkConfiguration | None: def parse_arg(config: _NetworkConfigurationSerialization) -> NetworkConfiguration | None:
nic_type = config.get("type", None) nic_type = config.get('type', None)
if not nic_type: if not nic_type:
return None return None
@ -128,7 +128,7 @@ class NetworkConfiguration:
case NicType.NM: case NicType.NM:
return NetworkConfiguration(NicType.NM) return NetworkConfiguration(NicType.NM)
case NicType.MANUAL: case NicType.MANUAL:
nics_arg = config.get("nics", []) nics_arg = config.get('nics', [])
if nics_arg: if nics_arg:
nics = [Nic.parse_arg(n) for n in nics_arg] nics = [Nic.parse_arg(n) for n in nics_arg]
return NetworkConfiguration(NicType.MANUAL, nics) return NetworkConfiguration(NicType.MANUAL, nics)
@ -146,14 +146,14 @@ class NetworkConfiguration:
enable_services=True, # Sources the ISO network configuration to the install medium. enable_services=True, # Sources the ISO network configuration to the install medium.
) )
case NicType.NM: case NicType.NM:
installation.add_additional_packages(["networkmanager"]) installation.add_additional_packages(['networkmanager'])
if profile_config and profile_config.profile: if profile_config and profile_config.profile:
if profile_config.profile.is_desktop_profile(): if profile_config.profile.is_desktop_profile():
installation.add_additional_packages(["network-manager-applet"]) installation.add_additional_packages(['network-manager-applet'])
installation.enable_service("NetworkManager.service") installation.enable_service('NetworkManager.service')
case NicType.MANUAL: case NicType.MANUAL:
for nic in self.nics: for nic in self.nics:
installation.configure_nic(nic) installation.configure_nic(nic)
installation.enable_service("systemd-networkd") installation.enable_service('systemd-networkd')
installation.enable_service("systemd-resolved") installation.enable_service('systemd-resolved')

View File

@ -9,10 +9,10 @@ from archinstall.lib.translationhandler import tr
class Repository(Enum): class Repository(Enum):
Core = "core" Core = 'core'
Extra = "extra" Extra = 'extra'
Multilib = "multilib" Multilib = 'multilib'
Testing = "testing" Testing = 'testing'
def get_repository_list(self) -> list[str]: def get_repository_list(self) -> list[str]:
match self: match self:
@ -24,9 +24,9 @@ class Repository(Enum):
return [Repository.Multilib.value] return [Repository.Multilib.value]
case Repository.Testing: case Repository.Testing:
return [ return [
"core-testing", 'core-testing',
"extra-testing", 'extra-testing',
"multilib-testing", 'multilib-testing',
] ]
@ -60,7 +60,7 @@ class PackageSearchResult:
checkdepends: list[str] checkdepends: list[str]
@staticmethod @staticmethod
def from_json(data: dict[str, Any]) -> "PackageSearchResult": def from_json(data: dict[str, Any]) -> 'PackageSearchResult':
return PackageSearchResult(**data) return PackageSearchResult(**data)
@property @property
@ -74,7 +74,7 @@ class PackageSearchResult:
return self.pkg_version == other.pkg_version return self.pkg_version == other.pkg_version
def __lt__(self, other: "PackageSearchResult") -> bool: def __lt__(self, other: 'PackageSearchResult') -> bool:
return self.pkg_version < other.pkg_version return self.pkg_version < other.pkg_version
@ -88,15 +88,15 @@ class PackageSearch:
results: list[PackageSearchResult] results: list[PackageSearchResult]
@staticmethod @staticmethod
def from_json(data: dict[str, Any]) -> "PackageSearch": def from_json(data: dict[str, Any]) -> 'PackageSearch':
results = [PackageSearchResult.from_json(r) for r in data["results"]] results = [PackageSearchResult.from_json(r) for r in data['results']]
return PackageSearch( return PackageSearch(
version=data["version"], version=data['version'],
limit=data["limit"], limit=data['limit'],
valid=data["valid"], valid=data['valid'],
num_pages=data["num_pages"], num_pages=data['num_pages'],
page=data["page"], page=data['page'],
results=results, results=results,
) )
@ -132,7 +132,7 @@ class LocalPackage(BaseModel):
return self.version == other.version return self.version == other.version
def __lt__(self, other: "LocalPackage") -> bool: def __lt__(self, other: 'LocalPackage') -> bool:
return self.version < other.version return self.version < other.version
@ -161,11 +161,11 @@ class AvailablePackage(BaseModel):
# return all package info line by line # return all package info line by line
def info(self) -> str: def info(self) -> str:
output = "" output = ''
for key, value in self.model_dump().items(): for key, value in self.model_dump().items():
key = key.replace("_", " ").capitalize() key = key.replace('_', ' ').capitalize()
key = key.ljust(self.longest_key) key = key.ljust(self.longest_key)
output += f"{key} : {value}\n" output += f'{key} : {value}\n'
return output return output
@ -179,14 +179,14 @@ class PackageGroup:
def from_available_packages( def from_available_packages(
cls, cls,
packages: dict[str, AvailablePackage], packages: dict[str, AvailablePackage],
) -> dict[str, "PackageGroup"]: ) -> dict[str, 'PackageGroup']:
pkg_groups: dict[str, "PackageGroup"] = {} pkg_groups: dict[str, 'PackageGroup'] = {}
for pkg in packages.values(): for pkg in packages.values():
if "None" in pkg.groups: if 'None' in pkg.groups:
continue continue
groups = pkg.groups.split(" ") groups = pkg.groups.split(' ')
for group in groups: for group in groups:
# same group names have multiple spaces in between # same group names have multiple spaces in between
@ -199,6 +199,6 @@ class PackageGroup:
return pkg_groups return pkg_groups
def info(self) -> str: def info(self) -> str:
output = tr("Package group:") + "\n - " output = tr('Package group:') + '\n - '
output += "\n - ".join(self.packages) output += '\n - '.join(self.packages)
return output return output

View File

@ -27,18 +27,18 @@ class ProfileConfiguration:
from ..profile.profiles_handler import profile_handler from ..profile.profiles_handler import profile_handler
return { return {
"profile": profile_handler.to_json(self.profile), 'profile': profile_handler.to_json(self.profile),
"gfx_driver": self.gfx_driver.value if self.gfx_driver else None, 'gfx_driver': self.gfx_driver.value if self.gfx_driver else None,
"greeter": self.greeter.value if self.greeter else None, 'greeter': self.greeter.value if self.greeter else None,
} }
@classmethod @classmethod
def parse_arg(cls, arg: _ProfileConfigurationSerialization) -> "ProfileConfiguration": def parse_arg(cls, arg: _ProfileConfigurationSerialization) -> 'ProfileConfiguration':
from ..profile.profiles_handler import profile_handler from ..profile.profiles_handler import profile_handler
profile = profile_handler.parse_profile_config(arg["profile"]) profile = profile_handler.parse_profile_config(arg['profile'])
greeter = arg.get("greeter", None) greeter = arg.get('greeter', None)
gfx_driver = arg.get("gfx_driver", None) gfx_driver = arg.get('gfx_driver', None)
return ProfileConfiguration( return ProfileConfiguration(
profile, profile,

View File

@ -8,37 +8,37 @@ from ..crypt import crypt_yescrypt
class PasswordStrength(Enum): class PasswordStrength(Enum):
VERY_WEAK = "very weak" VERY_WEAK = 'very weak'
WEAK = "weak" WEAK = 'weak'
MODERATE = "moderate" MODERATE = 'moderate'
STRONG = "strong" STRONG = 'strong'
@property @property
@override @override
def value(self) -> str: # pylint: disable=invalid-overridden-method def value(self) -> str: # pylint: disable=invalid-overridden-method
match self: match self:
case PasswordStrength.VERY_WEAK: case PasswordStrength.VERY_WEAK:
return tr("very weak") return tr('very weak')
case PasswordStrength.WEAK: case PasswordStrength.WEAK:
return tr("weak") return tr('weak')
case PasswordStrength.MODERATE: case PasswordStrength.MODERATE:
return tr("moderate") return tr('moderate')
case PasswordStrength.STRONG: case PasswordStrength.STRONG:
return tr("strong") return tr('strong')
def color(self) -> str: def color(self) -> str:
match self: match self:
case PasswordStrength.VERY_WEAK: case PasswordStrength.VERY_WEAK:
return "red" return 'red'
case PasswordStrength.WEAK: case PasswordStrength.WEAK:
return "red" return 'red'
case PasswordStrength.MODERATE: case PasswordStrength.MODERATE:
return "yellow" return 'yellow'
case PasswordStrength.STRONG: case PasswordStrength.STRONG:
return "green" return 'green'
@classmethod @classmethod
def strength(cls, password: str) -> "PasswordStrength": def strength(cls, password: str) -> 'PasswordStrength':
digit = any(character.isdigit() for character in password) digit = any(character.isdigit() for character in password)
upper = any(character.isupper() for character in password) upper = any(character.isupper() for character in password)
lower = any(character.islower() for character in password) lower = any(character.islower() for character in password)
@ -53,7 +53,7 @@ class PasswordStrength(Enum):
lower: bool, lower: bool,
symbol: bool, symbol: bool,
length: int, length: int,
) -> "PasswordStrength": ) -> 'PasswordStrength':
# suggested evaluation # suggested evaluation
# https://github.com/archlinux/archinstall/issues/1304#issuecomment-1146768163 # https://github.com/archlinux/archinstall/issues/1304#issuecomment-1146768163
if digit and upper and lower and symbol: if digit and upper and lower and symbol:
@ -101,13 +101,13 @@ class PasswordStrength(Enum):
_UserSerialization = TypedDict( _UserSerialization = TypedDict(
"_UserSerialization", '_UserSerialization',
{ {
"username": str, 'username': str,
"!password": NotRequired[str], '!password': NotRequired[str],
"sudo": bool, 'sudo': bool,
"groups": list[str], 'groups': list[str],
"enc_password": str | None, 'enc_password': str | None,
}, },
) )
@ -115,14 +115,14 @@ _UserSerialization = TypedDict(
class Password: class Password:
def __init__( def __init__(
self, self,
plaintext: str = "", plaintext: str = '',
enc_password: str | None = None, enc_password: str | None = None,
): ):
if plaintext: if plaintext:
enc_password = crypt_yescrypt(plaintext) enc_password = crypt_yescrypt(plaintext)
if not plaintext and not enc_password: if not plaintext and not enc_password:
raise ValueError("Either plaintext or enc_password must be provided") raise ValueError('Either plaintext or enc_password must be provided')
self._plaintext = plaintext self._plaintext = plaintext
self.enc_password = enc_password self.enc_password = enc_password
@ -148,9 +148,9 @@ class Password:
def hidden(self) -> str: def hidden(self) -> str:
if self._plaintext: if self._plaintext:
return "*" * len(self._plaintext) return '*' * len(self._plaintext)
else: else:
return "*" * 8 return '*' * 8
@dataclass @dataclass
@ -163,37 +163,37 @@ class User:
@override @override
def __str__(self) -> str: def __str__(self) -> str:
# safety overwrite to make sure password is not leaked # safety overwrite to make sure password is not leaked
return f"User({self.username=}, {self.sudo=}, {self.groups=})" return f'User({self.username=}, {self.sudo=}, {self.groups=})'
def table_data(self) -> dict[str, str | bool | list[str]]: def table_data(self) -> dict[str, str | bool | list[str]]:
return { return {
"username": self.username, 'username': self.username,
"password": self.password.hidden(), 'password': self.password.hidden(),
"sudo": self.sudo, 'sudo': self.sudo,
"groups": self.groups, 'groups': self.groups,
} }
def json(self) -> _UserSerialization: def json(self) -> _UserSerialization:
return { return {
"username": self.username, 'username': self.username,
"enc_password": self.password.enc_password, 'enc_password': self.password.enc_password,
"sudo": self.sudo, 'sudo': self.sudo,
"groups": self.groups, 'groups': self.groups,
} }
@classmethod @classmethod
def parse_arguments( def parse_arguments(
cls, cls,
args: list[_UserSerialization], args: list[_UserSerialization],
) -> list["User"]: ) -> list['User']:
users: list[User] = [] users: list[User] = []
for entry in args: for entry in args:
username = entry.get("username") username = entry.get('username')
password: Password | None = None password: Password | None = None
groups = entry.get("groups", []) groups = entry.get('groups', [])
plaintext = entry.get("!password") plaintext = entry.get('!password')
enc_password = entry.get("enc_password") enc_password = entry.get('enc_password')
# DEPRECATED: backwards compatibility # DEPRECATED: backwards compatibility
if plaintext: if plaintext:
@ -207,7 +207,7 @@ class User:
user = User( user = User(
username=username, username=username,
password=password, password=password,
sudo=entry.get("sudo", False) is True, sudo=entry.get('sudo', False) is True,
groups=groups, groups=groups,
) )

View File

@ -39,7 +39,7 @@ class DownloadTimer:
""" """
Raise the DownloadTimeout exception. Raise the DownloadTimeout exception.
""" """
raise DownloadTimeout(f"Download timed out after {self.timeout} second(s).") raise DownloadTimeout(f'Download timed out after {self.timeout} second(s).')
def __enter__(self) -> Self: def __enter__(self) -> Self:
if self.timeout > 0: if self.timeout > 0:
@ -72,27 +72,27 @@ def get_hw_addr(ifname: str) -> str:
import fcntl import fcntl
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ret = fcntl.ioctl(s.fileno(), 0x8927, struct.pack("256s", bytes(ifname, "utf-8")[:15])) ret = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', bytes(ifname, 'utf-8')[:15]))
return ":".join(f"{b:02x}" for b in ret[18:24]) return ':'.join(f'{b:02x}' for b in ret[18:24])
def list_interfaces(skip_loopback: bool = True) -> dict[str, str]: def list_interfaces(skip_loopback: bool = True) -> dict[str, str]:
interfaces = {} interfaces = {}
for _index, iface in socket.if_nameindex(): for _index, iface in socket.if_nameindex():
if skip_loopback and iface == "lo": if skip_loopback and iface == 'lo':
continue continue
mac = get_hw_addr(iface).replace(":", "-").lower() mac = get_hw_addr(iface).replace(':', '-').lower()
interfaces[mac] = iface interfaces[mac] = iface
return interfaces return interfaces
def update_keyring() -> bool: def update_keyring() -> bool:
info("Updating archlinux-keyring ...") info('Updating archlinux-keyring ...')
try: try:
Pacman.run("-Sy --noconfirm archlinux-keyring") Pacman.run('-Sy --noconfirm archlinux-keyring')
return True return True
except SysCallError: except SysCallError:
if os.geteuid() != 0: if os.geteuid() != 0:
@ -105,18 +105,18 @@ def enrich_iface_types(interfaces: list[str]) -> dict[str, str]:
result = {} result = {}
for iface in interfaces: for iface in interfaces:
if os.path.isdir(f"/sys/class/net/{iface}/bridge/"): if os.path.isdir(f'/sys/class/net/{iface}/bridge/'):
result[iface] = "BRIDGE" result[iface] = 'BRIDGE'
elif os.path.isfile(f"/sys/class/net/{iface}/tun_flags"): elif os.path.isfile(f'/sys/class/net/{iface}/tun_flags'):
# ethtool -i {iface} # ethtool -i {iface}
result[iface] = "TUN/TAP" result[iface] = 'TUN/TAP'
elif os.path.isdir(f"/sys/class/net/{iface}/device"): elif os.path.isdir(f'/sys/class/net/{iface}/device'):
if os.path.isdir(f"/sys/class/net/{iface}/wireless/"): if os.path.isdir(f'/sys/class/net/{iface}/wireless/'):
result[iface] = "WIRELESS" result[iface] = 'WIRELESS'
else: else:
result[iface] = "PHYSICAL" result[iface] = 'PHYSICAL'
else: else:
result[iface] = "UNKNOWN" result[iface] = 'UNKNOWN'
return result return result
@ -128,25 +128,25 @@ def fetch_data_from_url(url: str, params: dict[str, str] | None = None) -> str:
if params is not None: if params is not None:
encoded = urlencode(params) encoded = urlencode(params)
full_url = f"{url}?{encoded}" full_url = f'{url}?{encoded}'
else: else:
full_url = url full_url = url
try: try:
response = urlopen(full_url, context=ssl_context) response = urlopen(full_url, context=ssl_context)
data = response.read().decode("UTF-8") data = response.read().decode('UTF-8')
return data return data
except URLError as e: except URLError as e:
raise ValueError(f"Unable to fetch data from url: {url}\n{e}") raise ValueError(f'Unable to fetch data from url: {url}\n{e}')
except Exception as e: except Exception as e:
raise ValueError(f"Unexpected error when parsing response: {e}") raise ValueError(f'Unexpected error when parsing response: {e}')
def calc_checksum(icmp_packet: bytes) -> int: def calc_checksum(icmp_packet: bytes) -> int:
# Calculate the ICMP checksum # Calculate the ICMP checksum
checksum = 0 checksum = 0
for i in range(0, len(icmp_packet), 2): for i in range(0, len(icmp_packet), 2):
checksum += (icmp_packet[i] << 8) + (struct.unpack("B", icmp_packet[i + 1 : i + 2])[0] if len(icmp_packet[i + 1 : i + 2]) else 0) checksum += (icmp_packet[i] << 8) + (struct.unpack('B', icmp_packet[i + 1 : i + 2])[0] if len(icmp_packet[i + 1 : i + 2]) else 0)
checksum = (checksum >> 16) + (checksum & 0xFFFF) checksum = (checksum >> 16) + (checksum & 0xFFFF)
checksum = ~checksum & 0xFFFF checksum = ~checksum & 0xFFFF
@ -156,17 +156,17 @@ def calc_checksum(icmp_packet: bytes) -> int:
def build_icmp(payload: bytes) -> bytes: def build_icmp(payload: bytes) -> bytes:
# Define the ICMP Echo Request packet # Define the ICMP Echo Request packet
icmp_packet = struct.pack("!BBHHH", 8, 0, 0, 0, 1) + payload icmp_packet = struct.pack('!BBHHH', 8, 0, 0, 0, 1) + payload
checksum = calc_checksum(icmp_packet) checksum = calc_checksum(icmp_packet)
return struct.pack("!BBHHH", 8, 0, checksum, 0, 1) + payload return struct.pack('!BBHHH', 8, 0, checksum, 0, 1) + payload
def ping(hostname, timeout: int = 5) -> int: def ping(hostname, timeout: int = 5) -> int:
watchdog = select.epoll() watchdog = select.epoll()
started = time.time() started = time.time()
random_identifier = f"archinstall-{random.randint(1000, 9999)}".encode() random_identifier = f'archinstall-{random.randint(1000, 9999)}'.encode()
# Create a raw socket (requires root, which should be fine on archiso) # Create a raw socket (requires root, which should be fine on archiso)
icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
@ -184,14 +184,14 @@ def ping(hostname, timeout: int = 5) -> int:
try: try:
for _fileno, _event in watchdog.poll(0.1): for _fileno, _event in watchdog.poll(0.1):
response, _ = icmp_socket.recvfrom(1024) response, _ = icmp_socket.recvfrom(1024)
icmp_type = struct.unpack("!B", response[20:21])[0] icmp_type = struct.unpack('!B', response[20:21])[0]
# Check if it's an Echo Reply (ICMP type 0) # Check if it's an Echo Reply (ICMP type 0)
if icmp_type == 0 and response[-len(random_identifier) :] == random_identifier: if icmp_type == 0 and response[-len(random_identifier) :] == random_identifier:
latency = round((time.time() - started) * 1000) latency = round((time.time() - started) * 1000)
break break
except OSError as e: except OSError as e:
debug(f"Error: {e}") debug(f'Error: {e}')
break break
icmp_socket.close() icmp_socket.close()

View File

@ -19,7 +19,7 @@ class FormattedOutput:
@classmethod @classmethod
def _get_values( def _get_values(
cls, cls,
o: "DataclassInstance", o: 'DataclassInstance',
class_formatter: str | Callable | None = None, # type: ignore[type-arg] class_formatter: str | Callable | None = None, # type: ignore[type-arg]
filter_list: list[str] = [], filter_list: list[str] = [],
) -> dict[str, Any]: ) -> dict[str, Any]:
@ -38,10 +38,10 @@ class FormattedOutput:
func = getattr(o, class_formatter) func = getattr(o, class_formatter)
return func(filter_list) return func(filter_list)
raise ValueError("Unsupported formatting call") raise ValueError('Unsupported formatting call')
elif hasattr(o, "table_data"): elif hasattr(o, 'table_data'):
return o.table_data() return o.table_data()
elif hasattr(o, "json"): elif hasattr(o, 'json'):
return o.json() return o.json()
elif is_dataclass(o): elif is_dataclass(o):
return asdict(o) return asdict(o)
@ -78,36 +78,36 @@ class FormattedOutput:
filter_list = list(column_width.keys()) filter_list = list(column_width.keys())
# create the header lines # create the header lines
output = "" output = ''
key_list = [] key_list = []
for key in filter_list: for key in filter_list:
width = column_width[key] width = column_width[key]
key = key.replace("!", "").replace("_", " ") key = key.replace('!', '').replace('_', ' ')
if capitalize: if capitalize:
key = key.capitalize() key = key.capitalize()
key_list.append(unicode_ljust(key, width)) key_list.append(unicode_ljust(key, width))
output += " | ".join(key_list) + "\n" output += ' | '.join(key_list) + '\n'
output += "-" * len(output) + "\n" output += '-' * len(output) + '\n'
# create the data lines # create the data lines
for record in raw_data: for record in raw_data:
obj_data = [] obj_data = []
for key in filter_list: for key in filter_list:
width = column_width.get(key, len(key)) width = column_width.get(key, len(key))
value = record.get(key, "") value = record.get(key, '')
if "!" in key: if '!' in key:
value = "*" * len(value) value = '*' * len(value)
if isinstance(value, int | float) or (isinstance(value, str) and value.isnumeric()): if isinstance(value, int | float) or (isinstance(value, str) and value.isnumeric()):
obj_data.append(unicode_rjust(str(value), width)) obj_data.append(unicode_rjust(str(value), width))
else: else:
obj_data.append(unicode_ljust(str(value), width)) obj_data.append(unicode_ljust(str(value), width))
output += " | ".join(obj_data) + "\n" output += ' | '.join(obj_data) + '\n'
return output return output
@ -117,14 +117,14 @@ class FormattedOutput:
Will format a list into a given number of columns Will format a list into a given number of columns
""" """
chunks = [] chunks = []
output = "" output = ''
for i in range(0, len(entries), cols): for i in range(0, len(entries), cols):
chunks.append(entries[i : i + cols]) chunks.append(entries[i : i + cols])
for row in chunks: for row in chunks:
out_fmt = "{: <30} " * len(row) out_fmt = '{: <30} ' * len(row)
output += out_fmt.format(*row) + "\n" output += out_fmt.format(*row) + '\n'
return output return output
@ -137,8 +137,8 @@ class Journald:
except ModuleNotFoundError: except ModuleNotFoundError:
return None return None
log_adapter = logging.getLogger("archinstall") log_adapter = logging.getLogger('archinstall')
log_fmt = logging.Formatter("[%(levelname)s]: %(message)s") log_fmt = logging.Formatter('[%(levelname)s]: %(message)s')
log_ch = systemd.journal.JournalHandler() log_ch = systemd.journal.JournalHandler()
log_ch.setFormatter(log_fmt) log_ch.setFormatter(log_fmt)
log_adapter.addHandler(log_ch) log_adapter.addHandler(log_ch)
@ -148,11 +148,11 @@ class Journald:
def _check_log_permissions() -> None: def _check_log_permissions() -> None:
filename = storage.get("LOG_FILE", None) filename = storage.get('LOG_FILE', None)
log_dir = storage.get("LOG_PATH", Path("./")) log_dir = storage.get('LOG_PATH', Path('./'))
if not filename: if not filename:
raise ValueError("No log file name defined") raise ValueError('No log file name defined')
log_file = log_dir / filename log_file = log_dir / filename
@ -160,17 +160,17 @@ def _check_log_permissions() -> None:
log_dir.mkdir(exist_ok=True, parents=True) log_dir.mkdir(exist_ok=True, parents=True)
log_file.touch(exist_ok=True) log_file.touch(exist_ok=True)
with log_file.open("a") as fp: with log_file.open('a') as fp:
fp.write("") fp.write('')
except PermissionError: except PermissionError:
# Fallback to creating the log file in the current folder # Fallback to creating the log file in the current folder
fallback_dir = Path("./").absolute() fallback_dir = Path('./').absolute()
fallback_log_file = fallback_dir / filename fallback_log_file = fallback_dir / filename
fallback_log_file.touch(exist_ok=True) fallback_log_file.touch(exist_ok=True)
storage["LOG_PATH"] = fallback_dir storage['LOG_PATH'] = fallback_dir
warn(f"Not enough permission to place log file at {log_file}, creating it in {fallback_log_file} instead") warn(f'Not enough permission to place log file at {log_file}, creating it in {fallback_log_file} instead')
def _supports_color() -> bool: def _supports_color() -> bool:
@ -183,20 +183,20 @@ def _supports_color() -> bool:
Return True if the running system's terminal supports color, Return True if the running system's terminal supports color,
and False otherwise. and False otherwise.
""" """
supported_platform = sys.platform != "win32" or "ANSICON" in os.environ supported_platform = sys.platform != 'win32' or 'ANSICON' in os.environ
# isatty is not always implemented, #6223. # isatty is not always implemented, #6223.
is_a_tty = hasattr(sys.stdout, "isatty") and sys.stdout.isatty() is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
return supported_platform and is_a_tty return supported_platform and is_a_tty
class Font(Enum): class Font(Enum):
bold = "1" bold = '1'
italic = "3" italic = '3'
underscore = "4" underscore = '4'
blink = "5" blink = '5'
reverse = "7" reverse = '7'
conceal = "8" conceal = '8'
def _stylize_output( def _stylize_output(
@ -215,29 +215,29 @@ def _stylize_output(
Adds styling to a text given a set of color arguments. Adds styling to a text given a set of color arguments.
""" """
colors = { colors = {
"black": "0", 'black': '0',
"red": "1", 'red': '1',
"green": "2", 'green': '2',
"yellow": "3", 'yellow': '3',
"blue": "4", 'blue': '4',
"magenta": "5", 'magenta': '5',
"cyan": "6", 'cyan': '6',
"white": "7", 'white': '7',
"teal": "8;5;109", # Extended 256-bit colors (not always supported) 'teal': '8;5;109', # Extended 256-bit colors (not always supported)
"orange": "8;5;208", # https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors 'orange': '8;5;208', # https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
"darkorange": "8;5;202", 'darkorange': '8;5;202',
"gray": "8;5;246", 'gray': '8;5;246',
"grey": "8;5;246", 'grey': '8;5;246',
"darkgray": "8;5;240", 'darkgray': '8;5;240',
"lightgray": "8;5;256", 'lightgray': '8;5;256',
} }
foreground = {key: f"3{colors[key]}" for key in colors} foreground = {key: f'3{colors[key]}' for key in colors}
background = {key: f"4{colors[key]}" for key in colors} background = {key: f'4{colors[key]}' for key in colors}
code_list = [] code_list = []
if text == "" and reset: if text == '' and reset:
return "\x1b[0m" return '\x1b[0m'
code_list.append(foreground[str(fg)]) code_list.append(foreground[str(fg)])
@ -247,15 +247,15 @@ def _stylize_output(
for o in font: for o in font:
code_list.append(o.value) code_list.append(o.value)
ansi = ";".join(code_list) ansi = ';'.join(code_list)
return f"\033[{ansi}m{text}\033[0m" return f'\033[{ansi}m{text}\033[0m'
def info( def info(
*msgs: str, *msgs: str,
level: int = logging.INFO, level: int = logging.INFO,
fg: str = "white", fg: str = 'white',
bg: str | None = None, bg: str | None = None,
reset: bool = False, reset: bool = False,
font: list[Font] = [], font: list[Font] = [],
@ -265,13 +265,13 @@ def info(
def _timestamp() -> str: def _timestamp() -> str:
now = datetime.now(tz=UTC) now = datetime.now(tz=UTC)
return now.strftime("%Y-%m-%d %H:%M:%S") return now.strftime('%Y-%m-%d %H:%M:%S')
def debug( def debug(
*msgs: str, *msgs: str,
level: int = logging.DEBUG, level: int = logging.DEBUG,
fg: str = "white", fg: str = 'white',
bg: str | None = None, bg: str | None = None,
reset: bool = False, reset: bool = False,
font: list[Font] = [], font: list[Font] = [],
@ -282,7 +282,7 @@ def debug(
def error( def error(
*msgs: str, *msgs: str,
level: int = logging.ERROR, level: int = logging.ERROR,
fg: str = "red", fg: str = 'red',
bg: str | None = None, bg: str | None = None,
reset: bool = False, reset: bool = False,
font: list[Font] = [], font: list[Font] = [],
@ -293,7 +293,7 @@ def error(
def warn( def warn(
*msgs: str, *msgs: str,
level: int = logging.WARNING, level: int = logging.WARNING,
fg: str = "yellow", fg: str = 'yellow',
bg: str | None = None, bg: str | None = None,
reset: bool = False, reset: bool = False,
font: list[Font] = [], font: list[Font] = [],
@ -304,7 +304,7 @@ def warn(
def log( def log(
*msgs: str, *msgs: str,
level: int = logging.INFO, level: int = logging.INFO,
fg: str = "white", fg: str = 'white',
bg: str | None = None, bg: str | None = None,
reset: bool = False, reset: bool = False,
font: list[Font] = [], font: list[Font] = [],
@ -313,19 +313,19 @@ def log(
# right from the beginning when the modules are loaded # right from the beginning when the modules are loaded
_check_log_permissions() _check_log_permissions()
text = orig_string = " ".join([str(x) for x in msgs]) text = orig_string = ' '.join([str(x) for x in msgs])
# Attempt to colorize the output if supported # Attempt to colorize the output if supported
# Insert default colors and override with **kwargs # Insert default colors and override with **kwargs
if _supports_color(): if _supports_color():
text = _stylize_output(text, fg, bg, reset, font) text = _stylize_output(text, fg, bg, reset, font)
log_file = storage["LOG_PATH"] / storage["LOG_FILE"] log_file = storage['LOG_PATH'] / storage['LOG_FILE']
with log_file.open("a") as fp: with log_file.open('a') as fp:
ts = _timestamp() ts = _timestamp()
level_name = logging.getLevelName(level) level_name = logging.getLevelName(level)
out = f"[{ts}] - {level_name} - {orig_string}\n" out = f'[{ts}] - {level_name} - {orig_string}\n'
fp.write(out) fp.write(out)
Journald.log(text, level=level) Journald.log(text, level=level)

View File

@ -1,11 +1,11 @@
from .packages import find_package, find_packages, group_search, installed_package, list_available_packages, package_search, validate_package_list from .packages import find_package, find_packages, group_search, installed_package, list_available_packages, package_search, validate_package_list
__all__ = [ __all__ = [
"find_package", 'find_package',
"find_packages", 'find_packages',
"group_search", 'group_search',
"installed_package", 'installed_package',
"list_available_packages", 'list_available_packages',
"package_search", 'package_search',
"validate_package_list", 'validate_package_list',
] ]

View File

@ -11,9 +11,9 @@ from ..models.packages import AvailablePackage, LocalPackage, PackageSearch, Pac
from ..output import debug from ..output import debug
from ..pacman import Pacman from ..pacman import Pacman
BASE_URL_PKG_SEARCH = "https://archlinux.org/packages/search/json/" BASE_URL_PKG_SEARCH = 'https://archlinux.org/packages/search/json/'
# BASE_URL_PKG_CONTENT = 'https://archlinux.org/packages/search/json/' # BASE_URL_PKG_CONTENT = 'https://archlinux.org/packages/search/json/'
BASE_GROUP_URL = "https://archlinux.org/groups/search/json/" BASE_GROUP_URL = 'https://archlinux.org/groups/search/json/'
def _make_request(url: str, params: dict[str, str]) -> addinfourl: def _make_request(url: str, params: dict[str, str]) -> addinfourl:
@ -22,7 +22,7 @@ def _make_request(url: str, params: dict[str, str]) -> addinfourl:
ssl_context.verify_mode = ssl.CERT_NONE ssl_context.verify_mode = ssl.CERT_NONE
encoded = urlencode(params) encoded = urlencode(params)
full_url = f"{url}?{encoded}" full_url = f'{url}?{encoded}'
return urlopen(full_url, context=ssl_context) return urlopen(full_url, context=ssl_context)
@ -30,7 +30,7 @@ def _make_request(url: str, params: dict[str, str]) -> addinfourl:
def group_search(name: str) -> list[PackageSearchResult]: def group_search(name: str) -> list[PackageSearchResult]:
# TODO UPSTREAM: Implement /json/ for the groups search # TODO UPSTREAM: Implement /json/ for the groups search
try: try:
response = _make_request(BASE_GROUP_URL, {"name": name}) response = _make_request(BASE_GROUP_URL, {'name': name})
except HTTPError as err: except HTTPError as err:
if err.code == 404: if err.code == 404:
return [] return []
@ -38,9 +38,9 @@ def group_search(name: str) -> list[PackageSearchResult]:
raise err raise err
# Just to be sure some code didn't slip through the exception # Just to be sure some code didn't slip through the exception
data = response.read().decode("utf-8") data = response.read().decode('utf-8')
return [PackageSearchResult(**package) for package in json.loads(data)["results"]] return [PackageSearchResult(**package) for package in json.loads(data)['results']]
def package_search(package: str) -> PackageSearch: def package_search(package: str) -> PackageSearch:
@ -50,12 +50,12 @@ def package_search(package: str) -> PackageSearch:
""" """
# TODO UPSTREAM: Implement bulk search, either support name=X&name=Y or split on space (%20 or ' ') # TODO UPSTREAM: Implement bulk search, either support name=X&name=Y or split on space (%20 or ' ')
# TODO: utilize pacman cache first, upstream second. # TODO: utilize pacman cache first, upstream second.
response = _make_request(BASE_URL_PKG_SEARCH, {"name": package}) response = _make_request(BASE_URL_PKG_SEARCH, {'name': package})
if response.code != 200: if response.code != 200:
raise PackageError(f"Could not locate package: [{response.code}] {response}") raise PackageError(f'Could not locate package: [{response.code}] {response}')
data = response.read().decode("UTF-8") data = response.read().decode('UTF-8')
json_data = json.loads(data) json_data = json.loads(data)
return PackageSearch.from_json(json_data) return PackageSearch.from_json(json_data)
@ -107,7 +107,7 @@ def validate_package_list(packages: list[str]) -> tuple[list[str], list[str]]:
def installed_package(package: str) -> LocalPackage | None: def installed_package(package: str) -> LocalPackage | None:
package_info = [] package_info = []
try: try:
package_info = Pacman.run(f"-Q --info {package}").decode().split("\n") package_info = Pacman.run(f'-Q --info {package}').decode().split('\n')
return _parse_package_output(package_info, LocalPackage) return _parse_package_output(package_info, LocalPackage)
except SysCallError: except SysCallError:
pass pass
@ -127,15 +127,15 @@ def list_available_packages(
filtered_repos = [name for repo in repositories for name in repo.get_repository_list()] filtered_repos = [name for repo in repositories for name in repo.get_repository_list()]
try: try:
Pacman.run("-Sy") Pacman.run('-Sy')
except Exception as e: except Exception as e:
debug(f"Failed to sync Arch Linux package database: {e}") debug(f'Failed to sync Arch Linux package database: {e}')
for line in Pacman.run("-S --info"): for line in Pacman.run('-S --info'):
dec_line = line.decode().strip() dec_line = line.decode().strip()
current_package.append(dec_line) current_package.append(dec_line)
if dec_line.startswith("Validated"): if dec_line.startswith('Validated'):
if current_package: if current_package:
avail_pkg = _parse_package_output(current_package, AvailablePackage) avail_pkg = _parse_package_output(current_package, AvailablePackage)
if avail_pkg.repository in filtered_repos: if avail_pkg.repository in filtered_repos:
@ -147,7 +147,7 @@ def list_available_packages(
@lru_cache(maxsize=128) @lru_cache(maxsize=128)
def _normalize_key_name(key: str) -> str: def _normalize_key_name(key: str) -> str:
return key.strip().lower().replace(" ", "_") return key.strip().lower().replace(' ', '_')
def _parse_package_output[PackageType: (AvailablePackage, LocalPackage)]( def _parse_package_output[PackageType: (AvailablePackage, LocalPackage)](
@ -157,8 +157,8 @@ def _parse_package_output[PackageType: (AvailablePackage, LocalPackage)](
package = {} package = {}
for line in package_meta: for line in package_meta:
if ":" in line: if ':' in line:
key, value = line.split(":", 1) key, value = line.split(':', 1)
key = _normalize_key_name(key) key = _normalize_key_name(key)
package[key] = value.strip() package[key] = value.strip()

View File

@ -18,26 +18,26 @@ class Pacman:
self.target = target self.target = target
@staticmethod @staticmethod
def run(args: str, default_cmd: str = "pacman") -> SysCommand: def run(args: str, default_cmd: str = 'pacman') -> SysCommand:
""" """
A centralized function to call `pacman` from. A centralized function to call `pacman` from.
It also protects us from colliding with other running pacman sessions (if used locally). It also protects us from colliding with other running pacman sessions (if used locally).
The grace period is set to 10 minutes before exiting hard if another pacman instance is running. The grace period is set to 10 minutes before exiting hard if another pacman instance is running.
""" """
pacman_db_lock = Path("/var/lib/pacman/db.lck") pacman_db_lock = Path('/var/lib/pacman/db.lck')
if pacman_db_lock.exists(): if pacman_db_lock.exists():
warn(tr("Pacman is already running, waiting maximum 10 minutes for it to terminate.")) warn(tr('Pacman is already running, waiting maximum 10 minutes for it to terminate.'))
started = time.time() started = time.time()
while pacman_db_lock.exists(): while pacman_db_lock.exists():
time.sleep(0.25) time.sleep(0.25)
if time.time() - started > (60 * 10): if time.time() - started > (60 * 10):
error(tr("Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.")) error(tr('Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.'))
exit(1) exit(1)
return SysCommand(f"{default_cmd} {args}") return SysCommand(f'{default_cmd} {args}')
def ask(self, error_message: str, bail_message: str, func: Callable, *args, **kwargs) -> None: # type: ignore[type-arg] def ask(self, error_message: str, bail_message: str, func: Callable, *args, **kwargs) -> None: # type: ignore[type-arg]
while True: while True:
@ -45,20 +45,20 @@ class Pacman:
func(*args, **kwargs) func(*args, **kwargs)
break break
except Exception as err: except Exception as err:
error(f"{error_message}: {err}") error(f'{error_message}: {err}')
if not self.silent and input("Would you like to re-try this download? (Y/n): ").lower().strip() in "y": if not self.silent and input('Would you like to re-try this download? (Y/n): ').lower().strip() in 'y':
continue continue
raise RequirementError(f"{bail_message}: {err}") raise RequirementError(f'{bail_message}: {err}')
def sync(self) -> None: def sync(self) -> None:
if self.synced: if self.synced:
return return
self.ask( self.ask(
"Could not sync a new package database", 'Could not sync a new package database',
"Could not sync mirrors", 'Could not sync mirrors',
self.run, self.run,
"-Syy", '-Syy',
default_cmd="pacman", default_cmd='pacman',
) )
self.synced = True self.synced = True
@ -68,22 +68,22 @@ class Pacman:
packages = [packages] packages = [packages]
for plugin in plugins.values(): for plugin in plugins.values():
if hasattr(plugin, "on_pacstrap"): if hasattr(plugin, 'on_pacstrap'):
if result := plugin.on_pacstrap(packages): if result := plugin.on_pacstrap(packages):
packages = result packages = result
info(f"Installing packages: {packages}") info(f'Installing packages: {packages}')
self.ask( self.ask(
"Could not strap in packages", 'Could not strap in packages',
"Pacstrap failed. See /var/log/archinstall/install.log or above message for error details", 'Pacstrap failed. See /var/log/archinstall/install.log or above message for error details',
SysCommand, SysCommand,
f"pacstrap -C /etc/pacman.conf -K {self.target} {' '.join(packages)} --noconfirm", f'pacstrap -C /etc/pacman.conf -K {self.target} {" ".join(packages)} --noconfirm',
peek_output=True, peek_output=True,
) )
__all__ = [ __all__ = [
"Pacman", 'Pacman',
"PacmanConfig", 'PacmanConfig',
] ]

View File

@ -7,10 +7,10 @@ from ..models.packages import Repository
class PacmanConfig: class PacmanConfig:
def __init__(self, target: Path | None): def __init__(self, target: Path | None):
self._config_path = Path("/etc") / "pacman.conf" self._config_path = Path('/etc') / 'pacman.conf'
if target: if target:
self._config_remote_path = target / "etc" / "pacman.conf" self._config_remote_path = target / 'etc' / 'pacman.conf'
self._repositories: list[Repository] = [] self._repositories: list[Repository] = []
@ -27,7 +27,7 @@ class PacmanConfig:
repos_to_enable = [] repos_to_enable = []
for repo in self._repositories: for repo in self._repositories:
if repo == Repository.Testing: if repo == Repository.Testing:
repos_to_enable.extend(["core-testing", "extra-testing", "multilib-testing"]) repos_to_enable.extend(['core-testing', 'extra-testing', 'multilib-testing'])
else: else:
repos_to_enable.append(repo.value) repos_to_enable.append(repo.value)
@ -35,18 +35,18 @@ class PacmanConfig:
for row, line in enumerate(content): for row, line in enumerate(content):
# Check if this is a commented repository section that needs to be enabled # Check if this is a commented repository section that needs to be enabled
match = re.match(r"^#\s*\[(.*)\]", line) match = re.match(r'^#\s*\[(.*)\]', line)
if match and match.group(1) in repos_to_enable: if match and match.group(1) in repos_to_enable:
# uncomment the repository section line, properly removing # and any spaces # uncomment the repository section line, properly removing # and any spaces
content[row] = re.sub(r"^#\s*", "", line) content[row] = re.sub(r'^#\s*', '', line)
# also uncomment the next line (Include statement) if it exists and is commented # also uncomment the next line (Include statement) if it exists and is commented
if row + 1 < len(content) and content[row + 1].lstrip().startswith("#"): if row + 1 < len(content) and content[row + 1].lstrip().startswith('#'):
content[row + 1] = re.sub(r"^#\s*", "", content[row + 1]) content[row + 1] = re.sub(r'^#\s*', '', content[row + 1])
# Write the modified content back to the file # Write the modified content back to the file
with open(self._config_path, "w") as f: with open(self._config_path, 'w') as f:
f.writelines(content) f.writelines(content)
def persist(self) -> None: def persist(self) -> None:

View File

@ -16,15 +16,15 @@ plugins = {}
# 1: List archinstall.plugin definitions # 1: List archinstall.plugin definitions
# 2: Load the plugin entrypoint # 2: Load the plugin entrypoint
# 3: Initiate the plugin and store it as .name in plugins # 3: Initiate the plugin and store it as .name in plugins
for plugin_definition in metadata.entry_points().select(group="archinstall.plugin"): for plugin_definition in metadata.entry_points().select(group='archinstall.plugin'):
plugin_entrypoint = plugin_definition.load() plugin_entrypoint = plugin_definition.load()
try: try:
plugins[plugin_definition.name] = plugin_entrypoint() plugins[plugin_definition.name] = plugin_entrypoint()
except Exception as err: except Exception as err:
error( error(
f"Error: {err}", f'Error: {err}',
f"The above error was detected when loading the plugin: {plugin_definition}", f'The above error was detected when loading the plugin: {plugin_definition}',
) )
@ -34,11 +34,11 @@ def _localize_path(path: Path) -> Path:
""" """
url = urllib.parse.urlparse(str(path)) url = urllib.parse.urlparse(str(path))
if url.scheme and url.scheme in ("https", "http"): if url.scheme and url.scheme in ('https', 'http'):
converted_path = Path(f"/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py") converted_path = Path(f'/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py')
with open(converted_path, "w") as temp_file: with open(converted_path, 'w') as temp_file:
temp_file.write(urllib.request.urlopen(url.geturl()).read().decode("utf-8")) temp_file.write(urllib.request.urlopen(url.geturl()).read().decode('utf-8'))
return converted_path return converted_path
else: else:
@ -49,7 +49,7 @@ def _import_via_path(path: Path, namespace: str | None = None) -> str | None:
if not namespace: if not namespace:
namespace = os.path.basename(path) namespace = os.path.basename(path)
if namespace == "__init__.py": if namespace == '__init__.py':
namespace = path.parent.name namespace = path.parent.name
try: try:
@ -62,8 +62,8 @@ def _import_via_path(path: Path, namespace: str | None = None) -> str | None:
return namespace return namespace
except Exception as err: except Exception as err:
error( error(
f"Error: {err}", f'Error: {err}',
f"The above error was detected when loading the plugin: {path}", f'The above error was detected when loading the plugin: {path}',
) )
try: try:
@ -84,36 +84,36 @@ def _find_nth(haystack: list[str], needle: str, n: int) -> int | None:
def load_plugin(path: Path) -> None: def load_plugin(path: Path) -> None:
namespace: str | None = None namespace: str | None = None
parsed_url = urllib.parse.urlparse(str(path)) parsed_url = urllib.parse.urlparse(str(path))
info(f"Loading plugin from url {parsed_url}") info(f'Loading plugin from url {parsed_url}')
# The Profile was not a direct match on a remote URL # The Profile was not a direct match on a remote URL
if not parsed_url.scheme: if not parsed_url.scheme:
# Path was not found in any known examples, check if it's an absolute path # Path was not found in any known examples, check if it's an absolute path
if os.path.isfile(path): if os.path.isfile(path):
namespace = _import_via_path(path) namespace = _import_via_path(path)
elif parsed_url.scheme in ("https", "http"): elif parsed_url.scheme in ('https', 'http'):
localized = _localize_path(path) localized = _localize_path(path)
namespace = _import_via_path(localized) namespace = _import_via_path(localized)
if namespace and namespace in sys.modules: if namespace and namespace in sys.modules:
# Version dependency via __archinstall__version__ variable (if present) in the plugin # Version dependency via __archinstall__version__ variable (if present) in the plugin
# Any errors in version inconsistency will be handled through normal error handling if not defined. # Any errors in version inconsistency will be handled through normal error handling if not defined.
if hasattr(sys.modules[namespace], "__archinstall__version__"): if hasattr(sys.modules[namespace], '__archinstall__version__'):
archinstall_major_and_minor_version = float(storage["__version__"][: _find_nth(storage["__version__"], ".", 2)]) archinstall_major_and_minor_version = float(storage['__version__'][: _find_nth(storage['__version__'], '.', 2)])
if sys.modules[namespace].__archinstall__version__ < archinstall_major_and_minor_version: if sys.modules[namespace].__archinstall__version__ < archinstall_major_and_minor_version:
error(f"Plugin {sys.modules[namespace]} does not support the current Archinstall version.") error(f'Plugin {sys.modules[namespace]} does not support the current Archinstall version.')
# Locate the plugin entry-point called Plugin() # Locate the plugin entry-point called Plugin()
# This in accordance with the entry_points() from setup.cfg above # This in accordance with the entry_points() from setup.cfg above
if hasattr(sys.modules[namespace], "Plugin"): if hasattr(sys.modules[namespace], 'Plugin'):
try: try:
plugins[namespace] = sys.modules[namespace].Plugin() plugins[namespace] = sys.modules[namespace].Plugin()
info(f"Plugin {plugins[namespace]} has been loaded.") info(f'Plugin {plugins[namespace]} has been loaded.')
except Exception as err: except Exception as err:
error( error(
f"Error: {err}", f'Error: {err}',
f"The above error was detected when initiating the plugin: {path}", f'The above error was detected when initiating the plugin: {path}',
) )
else: else:
warn(f"Plugin '{path}' is missing a valid entry-point or is corrupt.") warn(f"Plugin '{path}' is missing a valid entry-point or is corrupt.")

View File

@ -37,29 +37,29 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
def _define_menu_options(self) -> list[MenuItem]: def _define_menu_options(self) -> list[MenuItem]:
return [ return [
MenuItem( MenuItem(
text=tr("Type"), text=tr('Type'),
action=self._select_profile, action=self._select_profile,
value=self._profile_config.profile, value=self._profile_config.profile,
preview_action=self._preview_profile, preview_action=self._preview_profile,
key="profile", key='profile',
), ),
MenuItem( MenuItem(
text=tr("Graphics driver"), text=tr('Graphics driver'),
action=self._select_gfx_driver, action=self._select_gfx_driver,
value=self._profile_config.gfx_driver if self._profile_config.profile and self._profile_config.profile.is_graphic_driver_supported() else None, value=self._profile_config.gfx_driver if self._profile_config.profile and self._profile_config.profile.is_graphic_driver_supported() else None,
preview_action=self._prev_gfx, preview_action=self._prev_gfx,
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False, enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
dependencies=["profile"], dependencies=['profile'],
key="gfx_driver", key='gfx_driver',
), ),
MenuItem( MenuItem(
text=tr("Greeter"), text=tr('Greeter'),
action=lambda x: select_greeter(preset=x), action=lambda x: select_greeter(preset=x),
value=self._profile_config.greeter if self._profile_config.profile and self._profile_config.profile.is_greeter_supported() else None, value=self._profile_config.greeter if self._profile_config.profile and self._profile_config.profile.is_greeter_supported() else None,
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False, enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
preview_action=self._prev_greeter, preview_action=self._prev_greeter,
dependencies=["profile"], dependencies=['profile'],
key="greeter", key='greeter',
), ),
] ]
@ -73,36 +73,36 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
if profile is not None: if profile is not None:
if not profile.is_graphic_driver_supported(): if not profile.is_graphic_driver_supported():
self._item_group.find_by_key("gfx_driver").enabled = False self._item_group.find_by_key('gfx_driver').enabled = False
self._item_group.find_by_key("gfx_driver").value = None self._item_group.find_by_key('gfx_driver').value = None
else: else:
self._item_group.find_by_key("gfx_driver").enabled = True self._item_group.find_by_key('gfx_driver').enabled = True
self._item_group.find_by_key("gfx_driver").value = GfxDriver.AllOpenSource self._item_group.find_by_key('gfx_driver').value = GfxDriver.AllOpenSource
if not profile.is_greeter_supported(): if not profile.is_greeter_supported():
self._item_group.find_by_key("greeter").enabled = False self._item_group.find_by_key('greeter').enabled = False
self._item_group.find_by_key("greeter").value = None self._item_group.find_by_key('greeter').value = None
else: else:
self._item_group.find_by_key("greeter").enabled = True self._item_group.find_by_key('greeter').enabled = True
self._item_group.find_by_key("greeter").value = profile.default_greeter_type self._item_group.find_by_key('greeter').value = profile.default_greeter_type
else: else:
self._item_group.find_by_key("gfx_driver").value = None self._item_group.find_by_key('gfx_driver').value = None
self._item_group.find_by_key("greeter").value = None self._item_group.find_by_key('greeter').value = None
return profile return profile
def _select_gfx_driver(self, preset: GfxDriver | None = None) -> GfxDriver | None: def _select_gfx_driver(self, preset: GfxDriver | None = None) -> GfxDriver | None:
driver = preset driver = preset
profile: Profile | None = self._item_group.find_by_key("profile").value profile: Profile | None = self._item_group.find_by_key('profile').value
if profile: if profile:
if profile.is_graphic_driver_supported(): if profile.is_graphic_driver_supported():
driver = select_driver(preset=preset) driver = select_driver(preset=preset)
if driver and "Sway" in profile.current_selection_names(): if driver and 'Sway' in profile.current_selection_names():
if driver.is_nvidia(): if driver.is_nvidia():
header = tr("The proprietary Nvidia driver is not supported by Sway.") + "\n" header = tr('The proprietary Nvidia driver is not supported by Sway.') + '\n'
header += tr("It is likely that you will run into issues, are you okay with that?") + "\n" header += tr('It is likely that you will run into issues, are you okay with that?') + '\n'
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
group.focus_item = MenuItem.no() group.focus_item = MenuItem.no()
@ -126,25 +126,25 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
if item.value: if item.value:
driver = item.get_value().value driver = item.get_value().value
packages = item.get_value().packages_text() packages = item.get_value().packages_text()
return f"Driver: {driver}\n{packages}" return f'Driver: {driver}\n{packages}'
return None return None
def _prev_greeter(self, item: MenuItem) -> str | None: def _prev_greeter(self, item: MenuItem) -> str | None:
if item.value: if item.value:
return f"{tr('Greeter')}: {item.value.value}" return f'{tr("Greeter")}: {item.value.value}'
return None return None
def _preview_profile(self, item: MenuItem) -> str | None: def _preview_profile(self, item: MenuItem) -> str | None:
profile: Profile | None = item.value profile: Profile | None = item.value
text = "" text = ''
if profile: if profile:
if (sub_profiles := profile.current_selection) is not None: if (sub_profiles := profile.current_selection) is not None:
text += tr("Selected profiles: ") text += tr('Selected profiles: ')
text += ", ".join([p.name for p in sub_profiles]) + "\n" text += ', '.join([p.name for p in sub_profiles]) + '\n'
if packages := profile.packages_text(include_sub_packages=True): if packages := profile.packages_text(include_sub_packages=True):
text += f"{packages}" text += f'{packages}'
if text: if text:
return text return text
@ -172,7 +172,7 @@ def select_greeter(
result = SelectMenu[GreeterType]( result = SelectMenu[GreeterType](
group, group,
allow_skip=True, allow_skip=True,
frame=FrameProperties.min(tr("Greeter")), frame=FrameProperties.min(tr('Greeter')),
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
).run() ).run()
@ -182,7 +182,7 @@ def select_greeter(
case ResultType.Selection: case ResultType.Selection:
return result.get_value() return result.get_value()
case ResultType.Reset: case ResultType.Reset:
raise ValueError("Unhandled result type") raise ValueError('Unhandled result type')
return None return None
@ -197,7 +197,7 @@ def select_profile(
top_level_profiles = profile_handler.get_top_level_profiles() top_level_profiles = profile_handler.get_top_level_profiles()
if header is None: if header is None:
header = tr("This is a list of pre-programmed default_profiles") + "\n" header = tr('This is a list of pre-programmed default_profiles') + '\n'
items = [MenuItem(p.name, value=p) for p in top_level_profiles] items = [MenuItem(p.name, value=p) for p in top_level_profiles]
group = MenuItemGroup(items, sort_items=True) group = MenuItemGroup(items, sort_items=True)
@ -209,7 +209,7 @@ def select_profile(
allow_reset=allow_reset, allow_reset=allow_reset,
allow_skip=True, allow_skip=True,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Main profile")), frame=FrameProperties.min(tr('Main profile')),
).run() ).run()
match result.type_: match result.type_:

View File

@ -46,13 +46,13 @@ class ProfileHandler:
if profile is not None: if profile is not None:
data = { data = {
"main": profile.name, 'main': profile.name,
"details": [profile.name for profile in profile.current_selection], 'details': [profile.name for profile in profile.current_selection],
"custom_settings": {profile.name: profile.custom_settings for profile in profile.current_selection}, 'custom_settings': {profile.name: profile.custom_settings for profile in profile.current_selection},
} }
if self._url_path is not None: if self._url_path is not None:
data["path"] = self._url_path data['path'] = self._url_path
return data return data
@ -66,7 +66,7 @@ class ProfileHandler:
# load all the default_profiles from url and custom # load all the default_profiles from url and custom
# so that we can then apply whatever was specified # so that we can then apply whatever was specified
# in the main/detail sections # in the main/detail sections
if url_path := profile_config.get("path", None): if url_path := profile_config.get('path', None):
self._url_path = url_path self._url_path = url_path
local_path = Path(url_path) local_path = Path(url_path)
@ -100,7 +100,7 @@ class ProfileHandler:
# if custom_profile := self.get_profile_by_name('Custom'): # if custom_profile := self.get_profile_by_name('Custom'):
# custom_profile.set_current_selection(custom_types) # custom_profile.set_current_selection(custom_types)
if main := profile_config.get("main", None): if main := profile_config.get('main', None):
profile = self.get_profile_by_name(main) if main else None profile = self.get_profile_by_name(main) if main else None
if not profile: if not profile:
@ -108,14 +108,14 @@ class ProfileHandler:
valid_sub_profiles: list[Profile] = [] valid_sub_profiles: list[Profile] = []
invalid_sub_profiles: list[str] = [] invalid_sub_profiles: list[str] = []
details: list[str] = profile_config.get("details", []) details: list[str] = profile_config.get('details', [])
if details: if details:
for detail in filter(None, details): for detail in filter(None, details):
# [2024-04-19] TODO: Backwards compatibility after naming change: https://github.com/archlinux/archinstall/pull/2421 # [2024-04-19] TODO: Backwards compatibility after naming change: https://github.com/archlinux/archinstall/pull/2421
# 'Kde' is deprecated, remove this block in a future version # 'Kde' is deprecated, remove this block in a future version
if detail == "Kde": if detail == 'Kde':
detail = "KDE Plasma" detail = 'KDE Plasma'
if sub_profile := self.get_profile_by_name(detail): if sub_profile := self.get_profile_by_name(detail):
valid_sub_profiles.append(sub_profile) valid_sub_profiles.append(sub_profile)
@ -123,9 +123,9 @@ class ProfileHandler:
invalid_sub_profiles.append(detail) invalid_sub_profiles.append(detail)
if invalid_sub_profiles: if invalid_sub_profiles:
info("No profile definition found: {}".format(", ".join(invalid_sub_profiles))) info('No profile definition found: {}'.format(', '.join(invalid_sub_profiles)))
custom_settings = profile_config.get("custom_settings", {}) custom_settings = profile_config.get('custom_settings', {})
profile.current_selection = valid_sub_profiles profile.current_selection = valid_sub_profiles
for sub_profile in valid_sub_profiles: for sub_profile in valid_sub_profiles:
@ -182,28 +182,28 @@ class ProfileHandler:
tailored = [p for p in self.profiles if p.is_tailored()] tailored = [p for p in self.profiles if p.is_tailored()]
return [t for t in tailored if t.name in self._local_mac_addresses] return [t for t in tailored if t.name in self._local_mac_addresses]
def install_greeter(self, install_session: "Installer", greeter: GreeterType) -> None: def install_greeter(self, install_session: 'Installer', greeter: GreeterType) -> None:
packages = [] packages = []
service = None service = None
match greeter: match greeter:
case GreeterType.LightdmSlick: case GreeterType.LightdmSlick:
packages = ["lightdm", "lightdm-slick-greeter"] packages = ['lightdm', 'lightdm-slick-greeter']
service = ["lightdm"] service = ['lightdm']
case GreeterType.Lightdm: case GreeterType.Lightdm:
packages = ["lightdm", "lightdm-gtk-greeter"] packages = ['lightdm', 'lightdm-gtk-greeter']
service = ["lightdm"] service = ['lightdm']
case GreeterType.Sddm: case GreeterType.Sddm:
packages = ["sddm"] packages = ['sddm']
service = ["sddm"] service = ['sddm']
case GreeterType.Gdm: case GreeterType.Gdm:
packages = ["gdm"] packages = ['gdm']
service = ["gdm"] service = ['gdm']
case GreeterType.Ly: case GreeterType.Ly:
packages = ["ly"] packages = ['ly']
service = ["ly"] service = ['ly']
case GreeterType.CosmicSession: case GreeterType.CosmicSession:
packages = ["cosmic-greeter"] packages = ['cosmic-greeter']
if packages: if packages:
install_session.add_additional_packages(packages) install_session.add_additional_packages(packages)
@ -212,35 +212,35 @@ class ProfileHandler:
# slick-greeter requires a config change # slick-greeter requires a config change
if greeter == GreeterType.LightdmSlick: if greeter == GreeterType.LightdmSlick:
path = install_session.target.joinpath("etc/lightdm/lightdm.conf") path = install_session.target.joinpath('etc/lightdm/lightdm.conf')
with open(path) as file: with open(path) as file:
filedata = file.read() filedata = file.read()
filedata = filedata.replace("#greeter-session=example-gtk-gnome", "greeter-session=lightdm-slick-greeter") filedata = filedata.replace('#greeter-session=example-gtk-gnome', 'greeter-session=lightdm-slick-greeter')
with open(path, "w") as file: with open(path, 'w') as file:
file.write(filedata) file.write(filedata)
def install_gfx_driver(self, install_session: "Installer", driver: GfxDriver) -> None: def install_gfx_driver(self, install_session: 'Installer', driver: GfxDriver) -> None:
debug(f"Installing GFX driver: {driver.value}") debug(f'Installing GFX driver: {driver.value}')
if driver in [GfxDriver.NvidiaOpenKernel, GfxDriver.NvidiaProprietary]: if driver in [GfxDriver.NvidiaOpenKernel, GfxDriver.NvidiaProprietary]:
headers = [f"{kernel}-headers" for kernel in install_session.kernels] headers = [f'{kernel}-headers' for kernel in install_session.kernels]
# Fixes https://github.com/archlinux/archinstall/issues/585 # Fixes https://github.com/archlinux/archinstall/issues/585
install_session.add_additional_packages(headers) install_session.add_additional_packages(headers)
elif driver in [GfxDriver.AllOpenSource, GfxDriver.AmdOpenSource]: elif driver in [GfxDriver.AllOpenSource, GfxDriver.AmdOpenSource]:
# The order of these two are important if amdgpu is installed #808 # The order of these two are important if amdgpu is installed #808
install_session.remove_mod("amdgpu") install_session.remove_mod('amdgpu')
install_session.remove_mod("radeon") install_session.remove_mod('radeon')
install_session.append_mod("amdgpu") install_session.append_mod('amdgpu')
install_session.append_mod("radeon") install_session.append_mod('radeon')
driver_pkgs = driver.gfx_packages() driver_pkgs = driver.gfx_packages()
pkg_names = [p.value for p in driver_pkgs] pkg_names = [p.value for p in driver_pkgs]
install_session.add_additional_packages(pkg_names) install_session.add_additional_packages(pkg_names)
def install_profile_config(self, install_session: "Installer", profile_config: ProfileConfiguration) -> None: def install_profile_config(self, install_session: 'Installer', profile_config: ProfileConfiguration) -> None:
profile = profile_config.profile profile = profile_config.profile
if not profile: if not profile:
@ -260,9 +260,9 @@ class ProfileHandler:
""" """
try: try:
data = fetch_data_from_url(url) data = fetch_data_from_url(url)
b_data = bytes(data, "utf-8") b_data = bytes(data, 'utf-8')
with NamedTemporaryFile(delete=False, suffix=".py") as fp: with NamedTemporaryFile(delete=False, suffix='.py') as fp:
fp.write(b_data) fp.write(b_data)
filepath = Path(fp.name) filepath = Path(fp.name)
@ -270,7 +270,7 @@ class ProfileHandler:
self.remove_custom_profiles(profiles) self.remove_custom_profiles(profiles)
self.add_custom_profiles(profiles) self.add_custom_profiles(profiles)
except ValueError: except ValueError:
err = tr("Unable to fetch profile from specified url: {}").format(url) err = tr('Unable to fetch profile from specified url: {}').format(url)
error(err) error(err)
def _load_profile_class(self, module: ModuleType) -> list[Profile]: def _load_profile_class(self, module: ModuleType) -> list[Profile]:
@ -288,7 +288,7 @@ class ProfileHandler:
if isinstance(cls_, Profile): if isinstance(cls_, Profile):
profiles.append(cls_) profiles.append(cls_)
except Exception: except Exception:
debug(f"Cannot import {module}, it does not appear to be a Profile class") debug(f'Cannot import {module}, it does not appear to be a Profile class')
return profiles return profiles
@ -301,7 +301,7 @@ class ProfileHandler:
duplicates = [x for x in counter.items() if x[1] != 1] duplicates = [x for x in counter.items() if x[1] != 1]
if len(duplicates) > 0: if len(duplicates) > 0:
err = tr("Profiles must have unique name, but profile definitions with duplicate name found: {}").format(duplicates[0][0]) err = tr('Profiles must have unique name, but profile definitions with duplicate name found: {}').format(duplicates[0][0])
error(err) error(err)
sys.exit(1) sys.exit(1)
@ -312,7 +312,7 @@ class ProfileHandler:
""" """
with open(file) as fp: with open(file) as fp:
for line in fp.readlines(): for line in fp.readlines():
if "__packages__" in line: if '__packages__' in line:
return True return True
return False return False
@ -321,15 +321,15 @@ class ProfileHandler:
Process a file for profile definitions Process a file for profile definitions
""" """
if self._is_legacy(file): if self._is_legacy(file):
info(f"Cannot import {file} because it is no longer supported, please use the new profile format") info(f'Cannot import {file} because it is no longer supported, please use the new profile format')
return [] return []
if not file.is_file(): if not file.is_file():
info(f"Cannot find profile file {file}") info(f'Cannot find profile file {file}')
return [] return []
name = file.name.removesuffix(file.suffix) name = file.name.removesuffix(file.suffix)
debug(f"Importing profile: {file}") debug(f'Importing profile: {file}')
try: try:
if spec := importlib.util.spec_from_file_location(name, file): if spec := importlib.util.spec_from_file_location(name, file):
@ -338,7 +338,7 @@ class ProfileHandler:
spec.loader.exec_module(imported) spec.loader.exec_module(imported)
return self._load_profile_class(imported) return self._load_profile_class(imported)
except Exception as e: except Exception as e:
error(f"Unable to parse file {file}: {e}") error(f'Unable to parse file {file}: {e}')
return [] return []
@ -346,11 +346,11 @@ class ProfileHandler:
""" """
Search the profile path for profile definitions Search the profile path for profile definitions
""" """
profiles_path = Path(__file__).parents[2] / "default_profiles" profiles_path = Path(__file__).parents[2] / 'default_profiles'
profiles = [] profiles = []
for file in profiles_path.glob("**/*.py"): for file in profiles_path.glob('**/*.py'):
# ignore the abstract default_profiles class # ignore the abstract default_profiles class
if "profile.py" in file.name: if 'profile.py' in file.name:
continue continue
profiles += self._process_profile_file(file) profiles += self._process_profile_file(file)

View File

@ -9,6 +9,6 @@ from pathlib import Path
from typing import Any from typing import Any
storage: dict[str, Any] = { storage: dict[str, Any] = {
"LOG_PATH": Path("/var/log/archinstall"), 'LOG_PATH': Path('/var/log/archinstall'),
"LOG_FILE": Path("install.log"), 'LOG_FILE': Path('install.log'),
} }

View File

@ -20,7 +20,7 @@ class Language:
@property @property
def display_name(self) -> str: def display_name(self) -> str:
name = self.name_en name = self.name_en
return f"{name} ({self.translation_percent}%)" return f'{name} ({self.translation_percent}%)'
def is_match(self, lang_or_translated_lang: str) -> bool: def is_match(self, lang_or_translated_lang: str) -> bool:
if self.name_en == lang_or_translated_lang: if self.name_en == lang_or_translated_lang:
@ -35,8 +35,8 @@ class Language:
class TranslationHandler: class TranslationHandler:
def __init__(self) -> None: def __init__(self) -> None:
self._base_pot = "base.pot" self._base_pot = 'base.pot'
self._languages = "languages.json" self._languages = 'languages.json'
self._total_messages = self._get_total_active_messages() self._total_messages = self._get_total_active_messages()
self._translated_languages = self._get_translations() self._translated_languages = self._get_translations()
@ -55,17 +55,17 @@ class TranslationHandler:
languages = [] languages = []
for short_form in defined_languages: for short_form in defined_languages:
mapping_entry: dict[str, str] = next(filter(lambda x: x["abbr"] == short_form, mappings)) mapping_entry: dict[str, str] = next(filter(lambda x: x['abbr'] == short_form, mappings))
abbr = mapping_entry["abbr"] abbr = mapping_entry['abbr']
lang = mapping_entry["lang"] lang = mapping_entry['lang']
translated_lang = mapping_entry.get("translated_lang", None) translated_lang = mapping_entry.get('translated_lang', None)
try: try:
# get a translation for a specific language # get a translation for a specific language
translation = gettext.translation("base", localedir=self._get_locales_dir(), languages=(abbr, lang)) translation = gettext.translation('base', localedir=self._get_locales_dir(), languages=(abbr, lang))
# calculate the percentage of total translated text to total number of messages # calculate the percentage of total translated text to total number of messages
if abbr == "en": if abbr == 'en':
percent = 100 percent = 100
else: else:
num_translations = self._get_catalog_size(translation) num_translations = self._get_catalog_size(translation)
@ -105,9 +105,9 @@ class TranslationHandler:
Get total messages that could be translated Get total messages that could be translated
""" """
locales = self._get_locales_dir() locales = self._get_locales_dir()
with open(f"{locales}/{self._base_pot}") as fp: with open(f'{locales}/{self._base_pot}') as fp:
lines = fp.readlines() lines = fp.readlines()
msgid_lines = [line for line in lines if "msgid" in line] msgid_lines = [line for line in lines if 'msgid' in line]
return len(msgid_lines) - 1 # don't count the first line which contains the metadata return len(msgid_lines) - 1 # don't count the first line which contains the metadata
@ -118,7 +118,7 @@ class TranslationHandler:
try: try:
return next(filter(lambda x: x.name_en == name, self._translated_languages)) return next(filter(lambda x: x.name_en == name, self._translated_languages))
except Exception: except Exception:
raise ValueError(f"No language with name found: {name}") raise ValueError(f'No language with name found: {name}')
def get_language_by_abbr(self, abbr: str) -> Language: def get_language_by_abbr(self, abbr: str) -> Language:
""" """
@ -141,7 +141,7 @@ class TranslationHandler:
Get the locales directory path Get the locales directory path
""" """
cur_path = Path(__file__).parent.parent cur_path = Path(__file__).parent.parent
locales_dir = Path.joinpath(cur_path, "locales") locales_dir = Path.joinpath(cur_path, 'locales')
return locales_dir return locales_dir
def _provided_translations(self) -> list[str]: def _provided_translations(self) -> list[str]:
@ -153,7 +153,7 @@ class TranslationHandler:
translation_files = [] translation_files = []
for filename in filenames: for filename in filenames:
if len(filename) == 2 or filename in ["pt_BR", "zh-CN", "zh-TW"]: if len(filename) == 2 or filename in ['pt_BR', 'zh-CN', 'zh-TW']:
translation_files.append(filename) translation_files.append(filename)
return translation_files return translation_files

View File

@ -4,7 +4,7 @@ from functools import lru_cache
@lru_cache(maxsize=128) @lru_cache(maxsize=128)
def _is_wide_character(char: str) -> bool: def _is_wide_character(char: str) -> bool:
return unicodedata.east_asian_width(char) in "FW" return unicodedata.east_asian_width(char) in 'FW'
def _count_wchars(string: str) -> int: def _count_wchars(string: str) -> int:
@ -12,7 +12,7 @@ def _count_wchars(string: str) -> int:
return sum(_is_wide_character(c) for c in string) return sum(_is_wide_character(c) for c in string)
def unicode_ljust(string: str, width: int, fillbyte: str = " ") -> str: def unicode_ljust(string: str, width: int, fillbyte: str = ' ') -> str:
"""Return a left-justified unicode string of length width. """Return a left-justified unicode string of length width.
>>> unicode_ljust('Hello', 15, '*') >>> unicode_ljust('Hello', 15, '*')
'Hello**********' 'Hello**********'
@ -26,7 +26,7 @@ def unicode_ljust(string: str, width: int, fillbyte: str = " ") -> str:
return string.ljust(width - _count_wchars(string), fillbyte) return string.ljust(width - _count_wchars(string), fillbyte)
def unicode_rjust(string: str, width: int, fillbyte: str = " ") -> str: def unicode_rjust(string: str, width: int, fillbyte: str = ' ') -> str:
"""Return a right-justified unicode string of length width. """Return a right-justified unicode string of length width.
>>> unicode_rjust('Hello', 15, '*') >>> unicode_rjust('Hello', 15, '*')
'**********Hello' '**********Hello'

View File

@ -20,7 +20,7 @@ def get_password(
while True: while True:
user_hdr = None user_hdr = None
if failure is not None: if failure is not None:
user_hdr = f"{header}\n{failure}\n" user_hdr = f'{header}\n{failure}\n'
elif header is not None: elif header is not None:
user_hdr = header user_hdr = header
@ -42,12 +42,12 @@ def get_password(
return password return password
if header is not None: if header is not None:
confirmation_header = f"{header}{tr('Password')}: {password.hidden()}\n" confirmation_header = f'{header}{tr("Password")}: {password.hidden()}\n'
else: else:
confirmation_header = f"{tr('Password')}: {password.hidden()}\n" confirmation_header = f'{tr("Password")}: {password.hidden()}\n'
result = EditMenu( result = EditMenu(
tr("Confirm password"), tr('Confirm password'),
header=confirmation_header, header=confirmation_header,
alignment=Alignment.CENTER, alignment=Alignment.CENTER,
allow_skip=False, allow_skip=False,
@ -57,7 +57,7 @@ def get_password(
if password._plaintext == result.text(): if password._plaintext == result.text():
return password return password
failure = tr("The confirmation password did not match, please try again") failure = tr('The confirmation password did not match, please try again')
def prompt_dir( def prompt_dir(
@ -73,7 +73,7 @@ def prompt_dir(
if dest_path.exists() and dest_path.is_dir(): if dest_path.exists() and dest_path.is_dir():
return None return None
return tr("Not a valid directory") return tr('Not a valid directory')
if validate: if validate:
validate_func = validate_path validate_func = validate_path
@ -108,9 +108,9 @@ def is_subpath(first: Path, second: Path) -> bool:
def format_cols(items: list[str], header: str | None = None) -> str: def format_cols(items: list[str], header: str | None = None) -> str:
if header: if header:
text = f"{header}:\n" text = f'{header}:\n'
else: else:
text = "" text = ''
nr_items = len(items) nr_items = len(items)
if nr_items <= 4: if nr_items <= 4:
@ -124,5 +124,5 @@ def format_cols(items: list[str], header: str | None = None) -> str:
text += FormattedOutput.as_columns(items, col) text += FormattedOutput.as_columns(items, col)
# remove whitespaces on each row # remove whitespaces on each row
text = "\n".join([t.strip() for t in text.split("\n")]) text = '\n'.join([t.strip() for t in text.split('\n')])
return text return text

View File

@ -31,7 +31,7 @@ def ask_user_questions() -> None:
global_menu = GlobalMenu(arch_config_handler.config) global_menu = GlobalMenu(arch_config_handler.config)
if not arch_config_handler.args.advanced: if not arch_config_handler.args.advanced:
global_menu.set_enabled("parallel_downloads", False) global_menu.set_enabled('parallel_downloads', False)
global_menu.run() global_menu.run()
@ -42,12 +42,12 @@ def perform_installation(mountpoint: Path) -> None:
Only requirement is that the block devices are Only requirement is that the block devices are
formatted and setup prior to entering this function. formatted and setup prior to entering this function.
""" """
info("Starting installation...") info('Starting installation...')
config = arch_config_handler.config config = arch_config_handler.config
if not config.disk_config: if not config.disk_config:
error("No disk configuration provided") error('No disk configuration provided')
return return
disk_config = config.disk_config disk_config = config.disk_config
@ -88,10 +88,10 @@ def perform_installation(mountpoint: Path) -> None:
installation.set_mirrors(mirror_config, on_target=True) installation.set_mirrors(mirror_config, on_target=True)
if config.swap: if config.swap:
installation.setup_swap("zram") installation.setup_swap('zram')
if config.bootloader == Bootloader.Grub and SysInfo.has_uefi(): if config.bootloader == Bootloader.Grub and SysInfo.has_uefi():
installation.add_additional_packages("grub") installation.add_additional_packages('grub')
installation.add_bootloader(config.bootloader, config.uki) installation.add_bootloader(config.bootloader, config.uki)
@ -112,9 +112,9 @@ def perform_installation(mountpoint: Path) -> None:
if audio_config: if audio_config:
audio_config.install_audio_config(installation) audio_config.install_audio_config(installation)
else: else:
info("No audio server will be installed") info('No audio server will be installed')
if config.packages and config.packages[0] != "": if config.packages and config.packages[0] != '':
installation.add_additional_packages(config.packages) installation.add_additional_packages(config.packages)
if profile_config := config.profile_config: if profile_config := config.profile_config:
@ -130,7 +130,7 @@ def perform_installation(mountpoint: Path) -> None:
installation.enable_espeakup() installation.enable_espeakup()
if root_pw := config.root_enc_password: if root_pw := config.root_enc_password:
root_user = User("root", root_pw, False) root_user = User('root', root_pw, False)
installation.set_user_password(root_user) installation.set_user_password(root_user)
if (profile_config := config.profile_config) and profile_config.profile: if (profile_config := config.profile_config) and profile_config.profile:
@ -147,7 +147,7 @@ def perform_installation(mountpoint: Path) -> None:
installation.genfstab() installation.genfstab()
debug(f"Disk states after installing:\n{disk_layouts()}") debug(f'Disk states after installing:\n{disk_layouts()}')
if not arch_config_handler.args.silent: if not arch_config_handler.args.silent:
with Tui(): with Tui():
@ -157,7 +157,7 @@ def perform_installation(mountpoint: Path) -> None:
case PostInstallationAction.EXIT: case PostInstallationAction.EXIT:
pass pass
case PostInstallationAction.REBOOT: case PostInstallationAction.REBOOT:
os.system("reboot") os.system('reboot')
case PostInstallationAction.CHROOT: case PostInstallationAction.CHROOT:
try: try:
installation.drop_to_shell() installation.drop_to_shell()
@ -179,7 +179,7 @@ def guided() -> None:
if not arch_config_handler.args.silent: if not arch_config_handler.args.silent:
with Tui(): with Tui():
if not config.confirm_config(): if not config.confirm_config():
debug("Installation aborted") debug('Installation aborted')
guided() guided()
if arch_config_handler.config.disk_config: if arch_config_handler.config.disk_config:

View File

@ -1,10 +1,10 @@
import glob import glob
from pathlib import Path from pathlib import Path
print("The following are viable --script options:") print('The following are viable --script options:')
for script in [Path(x) for x in glob.glob(f"{Path(__file__).parent}/*.py")]: for script in [Path(x) for x in glob.glob(f'{Path(__file__).parent}/*.py')]:
if script.stem in ["__init__", "list"]: if script.stem in ['__init__', 'list']:
continue continue
print(f" {script.stem}") print(f' {script.stem}')

View File

@ -19,7 +19,7 @@ def perform_installation(mountpoint: Path) -> None:
config = arch_config_handler.config config = arch_config_handler.config
if not config.disk_config: if not config.disk_config:
error("No disk configuration provided") error('No disk configuration provided')
return return
disk_config = config.disk_config disk_config = config.disk_config
@ -35,7 +35,7 @@ def perform_installation(mountpoint: Path) -> None:
# Strap in the base system, add a boot loader and configure # Strap in the base system, add a boot loader and configure
# some other minor details as specified by this profile and user. # some other minor details as specified by this profile and user.
if installation.minimal_installation(): if installation.minimal_installation():
installation.set_hostname("minimal-arch") installation.set_hostname('minimal-arch')
installation.add_bootloader(Bootloader.Systemd) installation.add_bootloader(Bootloader.Systemd)
network_config = config.network_config network_config = config.network_config
@ -46,19 +46,19 @@ def perform_installation(mountpoint: Path) -> None:
config.profile_config, config.profile_config,
) )
installation.add_additional_packages(["nano", "wget", "git"]) installation.add_additional_packages(['nano', 'wget', 'git'])
profile_config = ProfileConfiguration(MinimalProfile()) profile_config = ProfileConfiguration(MinimalProfile())
profile_handler.install_profile_config(installation, profile_config) profile_handler.install_profile_config(installation, profile_config)
user = User("devel", Password(plaintext="devel"), False) user = User('devel', Password(plaintext='devel'), False)
installation.create_users(user) installation.create_users(user)
# Once this is done, we output some useful information to the user # Once this is done, we output some useful information to the user
# And the installation is complete. # And the installation is complete.
info("There are two new accounts in your installation after reboot:") info('There are two new accounts in your installation after reboot:')
info(" * root (password: airoot)") info(' * root (password: airoot)')
info(" * devel (password: devel)") info(' * devel (password: devel)')
def _minimal() -> None: def _minimal() -> None:
@ -82,7 +82,7 @@ def _minimal() -> None:
if not arch_config_handler.args.silent: if not arch_config_handler.args.silent:
with Tui(): with Tui():
if not config.confirm_config(): if not config.confirm_config():
debug("Installation aborted") debug('Installation aborted')
_minimal() _minimal()
if arch_config_handler.config.disk_config: if arch_config_handler.config.disk_config:

View File

@ -15,11 +15,11 @@ def ask_user_questions() -> None:
global_menu = GlobalMenu(arch_config_handler.config) global_menu = GlobalMenu(arch_config_handler.config)
global_menu.disable_all() global_menu.disable_all()
global_menu.set_enabled("archinstall_language", True) global_menu.set_enabled('archinstall_language', True)
global_menu.set_enabled("disk_config", True) global_menu.set_enabled('disk_config', True)
global_menu.set_enabled("disk_encryption", True) global_menu.set_enabled('disk_encryption', True)
global_menu.set_enabled("swap", True) global_menu.set_enabled('swap', True)
global_menu.set_enabled("__config__", True) global_menu.set_enabled('__config__', True)
global_menu.run() global_menu.run()
@ -33,7 +33,7 @@ def perform_installation(mountpoint: Path) -> None:
config = arch_config_handler.config config = arch_config_handler.config
if not config.disk_config: if not config.disk_config:
error("No disk configuration provided") error('No disk configuration provided')
return return
disk_config = config.disk_config disk_config = config.disk_config
@ -52,12 +52,12 @@ def perform_installation(mountpoint: Path) -> None:
installation.mount_ordered_layout() installation.mount_ordered_layout()
# to generate a fstab directory holder. Avoids an error on exit and at the same time checks the procedure # to generate a fstab directory holder. Avoids an error on exit and at the same time checks the procedure
target = Path(f"{mountpoint}/etc/fstab") target = Path(f'{mountpoint}/etc/fstab')
if not target.parent.exists(): if not target.parent.exists():
target.parent.mkdir(parents=True) target.parent.mkdir(parents=True)
# For support reasons, we'll log the disk layout post installation (crash or no crash) # For support reasons, we'll log the disk layout post installation (crash or no crash)
debug(f"Disk states after installing:\n{disk_layouts()}") debug(f'Disk states after installing:\n{disk_layouts()}')
def _only_hd() -> None: def _only_hd() -> None:
@ -74,7 +74,7 @@ def _only_hd() -> None:
if not arch_config_handler.args.silent: if not arch_config_handler.args.silent:
with Tui(): with Tui():
if not config.confirm_config(): if not config.confirm_config():
debug("Installation aborted") debug('Installation aborted')
_only_hd() _only_hd()
if arch_config_handler.config.disk_config: if arch_config_handler.config.disk_config:

View File

@ -11,10 +11,10 @@ for p in profile_handler.get_mac_addr_profiles():
# that fits the requirements for this machine specifically). # that fits the requirements for this machine specifically).
info(f'Found a tailored profile for this machine called: "{p.name}"') info(f'Found a tailored profile for this machine called: "{p.name}"')
print("Starting install in:") print('Starting install in:')
for i in range(10, 0, -1): for i in range(10, 0, -1):
Tui.print(f"{i}...") Tui.print(f'{i}...')
time.sleep(1) time.sleep(1)
install_session = storage["installation_session"] install_session = storage['installation_session']
p.install(install_session) p.install(install_session)

Some files were not shown because too many files have changed in this diff Show More