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:
# 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"Processor model detected: {SysInfo.cpu_model()}")
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"Graphics devices detected: {SysInfo._graphics_devices().keys()}")
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'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'Graphics devices detected: {SysInfo._graphics_devices().keys()}')
# 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:
info("Fetching Arch Linux package database...")
info('Fetching Arch Linux package database...')
try:
Pacman.run("-Sy")
Pacman.run('-Sy')
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)
def _check_new_version() -> None:
info("Checking version...")
info('Checking version...')
upgrade = None
try:
upgrade = Pacman.run("-Qu archinstall").decode()
upgrade = Pacman.run('-Qu archinstall').decode()
except Exception as e:
debug(f"Failed determine pacman version: {e}")
debug(f'Failed determine pacman version: {e}')
if upgrade:
text = f"New version available: {upgrade}"
text = f'New version available: {upgrade}'
info(text)
time.sleep(3)
@ -65,12 +65,12 @@ def main() -> int:
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
"""
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()
return 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
_log_sys_info()
@ -83,7 +83,7 @@ def main() -> int:
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
importlib.import_module(mod_name)
@ -103,11 +103,11 @@ def run_as_a_module() -> None:
Tui.shutdown()
if exc:
err = "".join(traceback.format_exception(exc))
err = ''.join(traceback.format_exception(exc))
error(err)
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'
"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__ = [
"FormattedOutput",
"Language",
"Pacman",
"SysInfo",
"Tui",
"arch_config_handler",
"debug",
"disk_layouts",
"error",
"info",
"load_plugin",
"log",
"plugin",
"translation_handler",
"warn",
'FormattedOutput',
'Language',
'Pacman',
'SysInfo',
'Tui',
'arch_config_handler',
'debug',
'disk_layouts',
'error',
'info',
'load_plugin',
'log',
'plugin',
'translation_handler',
'warn',
]

View File

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

View File

@ -9,22 +9,22 @@ if TYPE_CHECKING:
class PipewireProfile(Profile):
def __init__(self) -> None:
super().__init__("Pipewire", ProfileType.Application)
super().__init__('Pipewire', ProfileType.Application)
@property
@override
def packages(self) -> list[str]:
return [
"pipewire",
"pipewire-alsa",
"pipewire-jack",
"pipewire-pulse",
"gst-plugin-pipewire",
"libpulse",
"wireplumber",
'pipewire',
'pipewire-alsa',
'pipewire-jack',
'pipewire-pulse',
'gst-plugin-pipewire',
'libpulse',
'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
users: list[User] | None = arch_config_handler.config.users
@ -34,24 +34,24 @@ class PipewireProfile(Profile):
for user in users:
# 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)
# 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
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,
)
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,
)
@override
def install(self, install_session: "Installer") -> None:
def install(self, install_session: 'Installer') -> None:
super().install(install_session)
install_session.add_additional_packages(self.packages)
self._enable_pipewire_for_all(install_session)

View File

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

View File

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

View File

@ -9,56 +9,56 @@ if TYPE_CHECKING:
class AwesomeProfile(XorgProfile):
def __init__(self) -> None:
super().__init__("Awesome", ProfileType.WindowMgr)
super().__init__('Awesome', ProfileType.WindowMgr)
@property
@override
def packages(self) -> list[str]:
return super().packages + [
"awesome",
"alacritty",
"xorg-xinit",
"xorg-xrandr",
"xterm",
"feh",
"slock",
"terminus-font",
"gnu-free-fonts",
"ttf-liberation",
"xsel",
'awesome',
'alacritty',
'xorg-xinit',
'xorg-xrandr',
'xterm',
'feh',
'slock',
'terminus-font',
'gnu-free-fonts',
'ttf-liberation',
'xsel',
]
@override
def install(self, install_session: "Installer") -> None:
def install(self, install_session: 'Installer') -> None:
super().install(install_session)
# 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()
# Replace xterm with alacritty for a smoother experience.
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)
# 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,
# 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()
for line in xinitrc_data.split("\n"):
if "twm &" in line:
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
if "xclock" in line:
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
if "xterm" in line:
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
for line in xinitrc_data.split('\n'):
if 'twm &' in line:
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
if 'xclock' in line:
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
if 'xterm' in line:
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
xinitrc_data += "\n"
xinitrc_data += "exec awesome\n"
xinitrc_data += '\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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,26 +12,26 @@ from archinstall.tui.types import Alignment, FrameProperties
class HyprlandProfile(XorgProfile):
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
@override
def packages(self) -> list[str]:
return [
"hyprland",
"dunst",
"kitty",
"uwsm",
"dolphin",
"wofi",
"xdg-desktop-portal-hyprland",
"qt5-wayland",
"qt6-wayland",
"polkit-kde-agent",
"grim",
"slurp",
'hyprland',
'dunst',
'kitty',
'uwsm',
'dolphin',
'wofi',
'xdg-desktop-portal-hyprland',
'qt5-wayland',
'qt6-wayland',
'polkit-kde-agent',
'grim',
'slurp',
]
@property
@ -42,32 +42,32 @@ class HyprlandProfile(XorgProfile):
@property
@override
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 []
def _ask_seat_access(self) -> None:
# 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 += "\n" + tr("Choose an option to give Hyprland access to your hardware") + "\n"
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'
items = [MenuItem(s.value, value=s) for s in SeatAccess]
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)
result = SelectMenu[SeatAccess](
group,
header=header,
allow_skip=False,
frame=FrameProperties.min(tr("Seat access")),
frame=FrameProperties.min(tr('Seat access')),
alignment=Alignment.CENTER,
).run()
if result.type_ == ResultType.Selection:
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
def do_on_select(self) -> None:

View File

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

View File

@ -13,22 +13,22 @@ from archinstall.tui.types import Alignment, FrameProperties
class LabwcProfile(XorgProfile):
def __init__(self) -> None:
super().__init__(
"Labwc",
'Labwc',
ProfileType.WindowMgr,
)
self.custom_settings = {"seat_access": None}
self.custom_settings = {'seat_access': None}
@property
@override
def packages(self) -> list[str]:
additional = []
if seat := self.custom_settings.get("seat_access", None):
if seat := self.custom_settings.get('seat_access', None):
additional = [seat]
return [
"alacritty",
"labwc",
'alacritty',
'labwc',
] + additional
@property
@ -39,32 +39,32 @@ class LabwcProfile(XorgProfile):
@property
@override
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 []
def _ask_seat_access(self) -> None:
# 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 += "\n" + tr("Choose an option to give labwc access to your hardware") + "\n"
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'
items = [MenuItem(s.value, value=s) for s in SeatAccess]
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)
result = SelectMenu[SeatAccess](
group,
header=header,
allow_skip=False,
frame=FrameProperties.min(tr("Seat access")),
frame=FrameProperties.min(tr('Seat access')),
alignment=Alignment.CENTER,
).run()
if result.type_ == ResultType.Selection:
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
def do_on_select(self) -> None:

View File

@ -6,7 +6,7 @@ from archinstall.default_profiles.xorg import XorgProfile
class LxqtProfile(XorgProfile):
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.
# LXQt works with lightdm, but since this is not supported, we will not default to this.
@ -15,13 +15,13 @@ class LxqtProfile(XorgProfile):
@override
def packages(self) -> list[str]:
return [
"lxqt",
"breeze-icons",
"oxygen-icons",
"xdg-utils",
"ttf-freefont",
"leafpad",
"slock",
'lxqt',
'breeze-icons',
'oxygen-icons',
'xdg-utils',
'ttf-freefont',
'leafpad',
'slock',
]
@property

View File

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

View File

@ -13,30 +13,30 @@ from archinstall.tui.types import Alignment, FrameProperties
class NiriProfile(XorgProfile):
def __init__(self) -> None:
super().__init__(
"Niri",
'Niri',
ProfileType.WindowMgr,
)
self.custom_settings = {"seat_access": None}
self.custom_settings = {'seat_access': None}
@property
@override
def packages(self) -> list[str]:
additional = []
if seat := self.custom_settings.get("seat_access", None):
if seat := self.custom_settings.get('seat_access', None):
additional = [seat]
return [
"niri",
"alacritty",
"fuzzel",
"mako",
"xorg-xwayland",
"waybar",
"swaybg",
"swayidle",
"swaylock",
"xdg-desktop-portal-gnome",
'niri',
'alacritty',
'fuzzel',
'mako',
'xorg-xwayland',
'waybar',
'swaybg',
'swayidle',
'swaylock',
'xdg-desktop-portal-gnome',
] + additional
@property
@ -47,32 +47,32 @@ class NiriProfile(XorgProfile):
@property
@override
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 []
def _ask_seat_access(self) -> None:
# 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 += "\n" + tr("Choose an option to give niri access to your hardware") + "\n"
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'
items = [MenuItem(s.value, value=s) for s in SeatAccess]
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)
result = SelectMenu[SeatAccess](
group,
header=header,
allow_skip=False,
frame=FrameProperties.min(tr("Seat access")),
frame=FrameProperties.min(tr('Seat access')),
alignment=Alignment.CENTER,
).run()
if result.type_ == ResultType.Selection:
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
def do_on_select(self) -> None:

View File

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

View File

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

View File

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

View File

@ -13,32 +13,32 @@ from archinstall.tui.types import Alignment, FrameProperties
class SwayProfile(XorgProfile):
def __init__(self) -> None:
super().__init__(
"Sway",
'Sway',
ProfileType.WindowMgr,
)
self.custom_settings = {"seat_access": None}
self.custom_settings = {'seat_access': None}
@property
@override
def packages(self) -> list[str]:
additional = []
if seat := self.custom_settings.get("seat_access", None):
if seat := self.custom_settings.get('seat_access', None):
additional = [seat]
return [
"sway",
"swaybg",
"swaylock",
"swayidle",
"waybar",
"wmenu",
"brightnessctl",
"grim",
"slurp",
"pavucontrol",
"foot",
"xorg-xwayland",
'sway',
'swaybg',
'swaylock',
'swayidle',
'waybar',
'wmenu',
'brightnessctl',
'grim',
'slurp',
'pavucontrol',
'foot',
'xorg-xwayland',
] + additional
@property
@ -49,32 +49,32 @@ class SwayProfile(XorgProfile):
@property
@override
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 []
def _ask_seat_access(self) -> None:
# 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 += "\n" + tr("Choose an option to give Sway access to your hardware") + "\n"
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'
items = [MenuItem(s.value, value=s) for s in SeatAccess]
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)
result = SelectMenu[SeatAccess](
group,
header=header,
allow_skip=False,
frame=FrameProperties.min(tr("Seat access")),
frame=FrameProperties.min(tr('Seat access')),
alignment=Alignment.CENTER,
).run()
if result.type_ == ResultType.Selection:
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
def do_on_select(self) -> None:

View File

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

View File

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

View File

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

View File

@ -12,31 +12,31 @@ if TYPE_CHECKING:
class ProfileType(Enum):
# top level default_profiles
Server = "Server"
Desktop = "Desktop"
Xorg = "Xorg"
Minimal = "Minimal"
Custom = "Custom"
Server = 'Server'
Desktop = 'Desktop'
Xorg = 'Xorg'
Minimal = 'Minimal'
Custom = 'Custom'
# detailed selection default_profiles
ServerType = "ServerType"
WindowMgr = "Window Manager"
DesktopEnv = "Desktop Environment"
CustomType = "CustomType"
ServerType = 'ServerType'
WindowMgr = 'Window Manager'
DesktopEnv = 'Desktop Environment'
CustomType = 'CustomType'
# special things
Tailored = "Tailored"
Application = "Application"
Tailored = 'Tailored'
Application = 'Application'
class GreeterType(Enum):
Lightdm = "lightdm-gtk-greeter"
LightdmSlick = "lightdm-slick-greeter"
Sddm = "sddm"
Gdm = "gdm"
Ly = "ly"
Lightdm = 'lightdm-gtk-greeter'
LightdmSlick = 'lightdm-slick-greeter'
Sddm = 'sddm'
Gdm = 'gdm'
Ly = 'ly'
# .. todo:: Remove when we un-hide cosmic behind --advanced
if "--advanced" in sys.argv:
CosmicSession = "cosmic-greeter"
if '--advanced' in sys.argv:
CosmicSession = 'cosmic-greeter'
class SelectResult(Enum):
@ -106,12 +106,12 @@ class Profile:
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
"""
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
finished and custom installation steps for specific default_profiles
@ -196,9 +196,9 @@ class Profile:
if sub_profile.packages:
packages.update(sub_profile.packages)
text = tr("Installed packages") + ":\n"
text = tr('Installed packages') + ':\n'
for pkg in sorted(packages):
text += f"\t- {pkg}\n"
text += f'\t- {pkg}\n'
return text

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class ServerProfile(Profile):
def __init__(self, current_value: list[Profile] = []):
super().__init__(
"Server",
'Server',
ProfileType.Server,
current_selection=current_value,
)
@ -39,8 +39,8 @@ class ServerProfile(Profile):
allow_reset=True,
allow_skip=True,
preview_style=PreviewStyle.RIGHT,
preview_size="auto",
preview_frame=FrameProperties.max("Info"),
preview_size='auto',
preview_frame=FrameProperties.max('Info'),
multi=True,
).run()
@ -55,20 +55,20 @@ class ServerProfile(Profile):
return SelectResult.ResetCurrent
@override
def post_install(self, install_session: "Installer") -> None:
def post_install(self, install_session: 'Installer') -> None:
for profile in self.current_selection:
profile.post_install(install_session)
@override
def install(self, install_session: "Installer") -> None:
def install(self, install_session: 'Installer') -> None:
server_info = self.current_selection_names()
details = ", ".join(server_info)
info(f"Now installing the selected servers: {details}")
details = ', '.join(server_info)
info(f'Now installing the selected servers: {details}')
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.enable_service(server.services)
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):
def __init__(self) -> None:
super().__init__(
"Cockpit",
'Cockpit',
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ["cockpit", "udisks2", "packagekit"]
return ['cockpit', 'udisks2', 'packagekit']
@property
@override
def services(self) -> list[str]:
return ["cockpit.socket"]
return ['cockpit.socket']

View File

@ -9,23 +9,23 @@ if TYPE_CHECKING:
class DockerProfile(Profile):
def __init__(self) -> None:
super().__init__(
"Docker",
'Docker',
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ["docker"]
return ['docker']
@property
@override
def services(self) -> list[str]:
return ["docker"]
return ['docker']
@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
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):
def __init__(self) -> None:
super().__init__(
"httpd",
'httpd',
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ["apache"]
return ['apache']
@property
@override
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):
def __init__(self) -> None:
super().__init__(
"Lighttpd",
'Lighttpd',
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ["lighttpd"]
return ['lighttpd']
@property
@override
def services(self) -> list[str]:
return ["lighttpd"]
return ['lighttpd']

View File

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

View File

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

View File

@ -9,14 +9,14 @@ if TYPE_CHECKING:
class TailoredProfile(XorgProfile):
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
@override
def packages(self) -> list[str]:
return ["nano", "wget", "git"]
return ['nano', 'wget', 'git']
@override
def install(self, install_session: "Installer") -> None:
def install(self, install_session: 'Installer') -> None:
super().install(install_session)
# do whatever you like here :)

View File

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

View File

@ -39,8 +39,8 @@ class Arguments:
creds_decryption_key: str | None = None
silent: bool = False
dry_run: bool = False
script: str = "guided"
mountpoint: Path = Path("/mnt")
script: str = 'guided'
mountpoint: Path = Path('/mnt')
skip_ntp: bool = False
skip_wkd: bool = False
debug: bool = False
@ -56,7 +56,7 @@ class Arguments:
class ArchConfig:
version: str | 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
profile_config: ProfileConfiguration | None = None
mirror_config: MirrorConfiguration | None = None
@ -64,13 +64,13 @@ class ArchConfig:
bootloader: Bootloader = field(default=Bootloader.get_default())
uki: bool = False
audio_config: AudioConfiguration | None = None
hostname: str = "archlinux"
kernels: list[str] = field(default_factory=lambda: ["linux"])
hostname: str = 'archlinux'
kernels: list[str] = field(default_factory=lambda: ['linux'])
ntp: bool = True
packages: list[str] = field(default_factory=list)
parallel_downloads: int = 0
swap: bool = True
timezone: str = "UTC"
timezone: str = 'UTC'
services: 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]:
config = {
"users": [user.json() for user in self.users],
"root_enc_password": self.root_enc_password.enc_password if self.root_enc_password else None,
'users': [user.json() for user in self.users],
'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:
config["encryption_password"] = self.disk_encryption.encryption_password.plaintext
config['encryption_password'] = self.disk_encryption.encryption_password.plaintext
return config
def safe_json(self) -> dict[str, Any]:
config: Any = {
"version": self.version,
"archinstall-language": self.archinstall_language.json(),
"hostname": self.hostname,
"kernels": self.kernels,
"ntp": self.ntp,
"packages": self.packages,
"parallel_downloads": self.parallel_downloads,
"swap": self.swap,
"timezone": self.timezone,
"services": self.services,
"custom_commands": self.custom_commands,
"bootloader": self.bootloader.json(),
"audio_config": self.audio_config.json() if self.audio_config else None,
'version': self.version,
'archinstall-language': self.archinstall_language.json(),
'hostname': self.hostname,
'kernels': self.kernels,
'ntp': self.ntp,
'packages': self.packages,
'parallel_downloads': self.parallel_downloads,
'swap': self.swap,
'timezone': self.timezone,
'services': self.services,
'custom_commands': self.custom_commands,
'bootloader': self.bootloader.json(),
'audio_config': self.audio_config.json() if self.audio_config else None,
}
if self.locale_config:
config["locale_config"] = self.locale_config.json()
config['locale_config'] = self.locale_config.json()
if self.disk_config:
config["disk_config"] = self.disk_config.json()
config['disk_config'] = self.disk_config.json()
if self.disk_encryption:
config["disk_encryption"] = self.disk_encryption.json()
config['disk_encryption'] = self.disk_encryption.json()
if self.profile_config:
config["profile_config"] = self.profile_config.json()
config['profile_config'] = self.profile_config.json()
if self.mirror_config:
config["mirror_config"] = self.mirror_config.json()
config['mirror_config'] = self.mirror_config.json()
if self.network_config:
config["network_config"] = self.network_config.json()
config['network_config'] = self.network_config.json()
return config
@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.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)
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)
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)
if mirror_config := args_config.get("mirror_config", None):
if mirror_config := args_config.get('mirror_config', None):
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]
arch_config.mirror_config = MirrorConfiguration.parse_args(
@ -152,62 +152,62 @@ class ArchConfig:
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)
# DEPRECATED: backwards copatibility
if users := args_config.get("!users", None):
if users := args_config.get('!users', None):
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)
if bootloader_config := args_config.get("bootloader", None):
if bootloader_config := args_config.get('bootloader', None):
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
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)
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_config,
args_config["disk_encryption"],
Password(plaintext=args_config.get("encryption_password", "")),
args_config['disk_encryption'],
Password(plaintext=args_config.get('encryption_password', '')),
)
if hostname := args_config.get("hostname", ""):
if hostname := args_config.get('hostname', ''):
arch_config.hostname = hostname
if kernels := args_config.get("kernels", []):
if kernels := args_config.get('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
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.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
if services := args_config.get("services", []):
if services := args_config.get('services', []):
arch_config.services = services
# 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)
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)
if custom_commands := args_config.get("custom_commands", []):
if custom_commands := args_config.get('custom_commands', []):
arch_config.custom_commands = custom_commands
return arch_config
@ -239,135 +239,135 @@ class ArchConfigHandler:
def _get_version(self) -> str:
try:
return version("archinstall")
return version('archinstall')
except Exception:
return "Archinstall version not found"
return 'Archinstall version not found'
def _define_arguments(self) -> ArgumentParser:
parser = ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
"-v",
"--version",
action="version",
'-v',
'--version',
action='version',
default=False,
version="%(prog)s " + self._get_version(),
version='%(prog)s ' + self._get_version(),
)
parser.add_argument(
"--config",
'--config',
type=Path,
nargs="?",
nargs='?',
default=None,
help="JSON configuration file",
help='JSON configuration file',
)
parser.add_argument(
"--config-url",
'--config-url',
type=str,
nargs="?",
nargs='?',
default=None,
help="Url to a JSON configuration file",
help='Url to a JSON configuration file',
)
parser.add_argument(
"--creds",
'--creds',
type=Path,
nargs="?",
nargs='?',
default=None,
help="JSON credentials configuration file",
help='JSON credentials configuration file',
)
parser.add_argument(
"--creds-url",
'--creds-url',
type=str,
nargs="?",
nargs='?',
default=None,
help="Url to a JSON credentials configuration file",
help='Url to a JSON credentials configuration file',
)
parser.add_argument(
"--creds-decryption-key",
'--creds-decryption-key',
type=str,
nargs="?",
nargs='?',
default=None,
help="Decryption key for credentials file",
help='Decryption key for credentials file',
)
parser.add_argument(
"--silent",
action="store_true",
'--silent',
action='store_true',
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(
"--dry-run",
"--dry_run",
action="store_true",
'--dry-run',
'--dry_run',
action='store_true',
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(
"--script",
default="guided",
nargs="?",
help="Script to run for installation",
'--script',
default='guided',
nargs='?',
help='Script to run for installation',
type=str,
)
parser.add_argument(
"--mountpoint",
'--mountpoint',
type=Path,
nargs="?",
default=Path("/mnt"),
help="Define an alternate mount point for installation",
nargs='?',
default=Path('/mnt'),
help='Define an alternate mount point for installation',
)
parser.add_argument(
"--skip-ntp",
action="store_true",
help="Disables NTP checks during installation",
'--skip-ntp',
action='store_true',
help='Disables NTP checks during installation',
default=False,
)
parser.add_argument(
"--skip-wkd",
action="store_true",
help="Disables checking if archlinux keyring wkd sync is complete.",
'--skip-wkd',
action='store_true',
help='Disables checking if archlinux keyring wkd sync is complete.',
default=False,
)
parser.add_argument(
"--debug",
action="store_true",
'--debug',
action='store_true',
default=False,
help="Adds debug info into the log",
help='Adds debug info into the log',
)
parser.add_argument(
"--offline",
action="store_true",
'--offline',
action='store_true',
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(
"--no-pkg-lookups",
action="store_true",
'--no-pkg-lookups',
action='store_true',
default=False,
help="Disabled package validation specifically prior to starting installation.",
help='Disabled package validation specifically prior to starting installation.',
)
parser.add_argument(
"--plugin",
nargs="?",
'--plugin',
nargs='?',
type=str,
default=None,
help="File path to a plugin to load",
help='File path to a plugin to load',
)
parser.add_argument(
"--skip-version-check",
action="store_true",
'--skip-version-check',
action='store_true',
default=False,
help="Skip the version check when running archinstall",
help='Skip the version check when running archinstall',
)
parser.add_argument(
"--advanced",
action="store_true",
'--advanced',
action='store_true',
default=False,
help="Enabled advanced options",
help='Enabled advanced options',
)
parser.add_argument(
"--verbose",
action="store_true",
'--verbose',
action='store_true',
default=False,
help="Enabled verbose options",
help='Enabled verbose options',
)
return parser
@ -382,15 +382,15 @@ class ArchConfigHandler:
args.silent = False
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:
plugin_path = Path(args.plugin)
load_plugin(plugin_path)
if args.creds_decryption_key is None:
if os.environ.get("ARCHINSTALL_CREDS_DECRYPTION_KEY"):
args.creds_decryption_key = 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')
return args
@ -422,27 +422,27 @@ class ArchConfigHandler:
return config
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:
try:
creds_data = decrypt(creds_data, self._args.creds_decryption_key)
return json.loads(creds_data)
except ValueError as err:
if "Invalid password" in str(err):
error(tr("Incorrect credentials file decryption password"))
if 'Invalid password' in str(err):
error(tr('Incorrect credentials file decryption password'))
exit(1)
else:
debug(f"Error decrypting credentials file: {err}")
debug(f'Error decrypting credentials file: {err}')
raise err from err
else:
incorrect_password = False
with Tui():
while True:
header = tr("Incorrect password") if incorrect_password else None
header = tr('Incorrect password') if incorrect_password else None
decryption_pwd = get_password(
text=tr("Credentials file decryption password"),
text=tr('Credentials file decryption password'),
header=header,
allow_skip=False,
skip_confirmation=True,
@ -455,11 +455,11 @@ class ArchConfigHandler:
creds_data = decrypt(creds_data, decryption_pwd.plaintext)
break
except ValueError as err:
if "Invalid password" in str(err):
debug("Incorrect credentials file decryption password")
if 'Invalid password' in str(err):
debug('Incorrect credentials file decryption password')
incorrect_password = True
else:
debug(f"Error decrypting credentials file: {err}")
debug(f'Error decrypting credentials file: {err}')
raise err from err
return json.loads(creds_data)
@ -467,19 +467,19 @@ class ArchConfigHandler:
def _fetch_from_url(self, url: str) -> str:
if urllib.parse.urlparse(url).scheme:
try:
req = Request(url, headers={"User-Agent": "ArchInstall"})
req = Request(url, headers={'User-Agent': 'ArchInstall'})
with urlopen(req) as resp:
return resp.read().decode("utf-8")
return resp.read().decode('utf-8')
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:
error("Not a valid url")
error('Not a valid url')
exit(1)
def _read_file(self, path: Path) -> str:
if not path.exists():
error(f"Could not find file {path}")
error(f'Could not find file {path}')
exit(1)
return path.read_text()

View File

@ -11,13 +11,13 @@ from .storage import storage
class Boot:
def __init__(self, installation: Installer):
self.instance = installation
self.container_name = "archinstall"
self.container_name = 'archinstall'
self.session: SysCommandWorker | None = None
self.ready = False
def __enter__(self) -> "Boot":
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.")
def __enter__(self) -> 'Boot':
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.')
if existing_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.
self.session = SysCommandWorker(
[
"systemd-nspawn",
"-D",
'systemd-nspawn',
'-D',
str(self.instance.target),
"--timezone=off",
"-b",
"--no-pager",
"--machine",
'--timezone=off',
'-b',
'--no-pager',
'--machine',
self.container_name,
]
)
if not self.ready and self.session:
while self.session.is_alive():
if b" login:" in self.session:
if b' login:' in self.session:
self.ready = True
break
storage["active_boot"] = self
storage['active_boot'] = self
return self
def __exit__(self, *args: str, **kwargs: str) -> None:
@ -54,14 +54,14 @@ class Boot:
if len(args) >= 2 and args[1]:
error(
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_exit_code: int | None = -1
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:
shutdown_exit_code = err.exit_code
@ -73,12 +73,12 @@ class Boot:
shutdown_exit_code = shutdown.exit_code
if self.session and (self.session.exit_code == 0 or shutdown_exit_code == 0):
storage["active_boot"] = None
storage['active_boot'] = None
else:
session_exit_code = self.session.exit_code if self.session else -1
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])),
)
@ -99,17 +99,17 @@ class Boot:
return self.session.is_alive()
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.
# However, that check is done for `machinectl` and not for our chroot command.
# So this wrapper for SysCommand will do this additionally.
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:
if cmd[0][0] != "/" and cmd[0][:2] != "./":
if cmd[0][0] != '/' and cmd[0][:2] != './':
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._default_save_path = storage.get("LOG_PATH", Path("."))
self._user_config_file = Path("user_configuration.json")
self._user_creds_file = Path("user_credentials.json")
self._default_save_path = storage.get('LOG_PATH', Path('.'))
self._user_config_file = Path('user_configuration.json')
self._user_creds_file = Path('user_credentials.json')
@property
def user_configuration_file(self) -> Path:
@ -50,12 +50,12 @@ class ConfigurationOutput:
return json.dumps(out, indent=4, sort_keys=True, cls=UNSAFE_JSON)
def write_debug(self) -> None:
debug(" -- Chosen configuration --")
debug(' -- Chosen configuration --')
debug(self.user_config_to_json())
def confirm_config(self) -> bool:
header = f"{tr('The specified configuration will be applied')}. "
header += tr("Would you like to continue?") + "\n"
header = f'{tr("The specified configuration will be applied")}. '
header += tr('Would you like to continue?') + '\n'
with Tui():
group = MenuItemGroup.yes_no()
@ -69,9 +69,9 @@ class ConfigurationOutput:
columns=2,
orientation=Orientation.HORIZONTAL,
allow_skip=False,
preview_size="auto",
preview_size='auto',
preview_style=PreviewStyle.BOTTOM,
preview_frame=FrameProperties.max(tr("Configuration")),
preview_frame=FrameProperties.max(tr('Configuration')),
).run()
if result.item() != MenuItem.yes():
@ -83,8 +83,8 @@ class ConfigurationOutput:
dest_path_ok = dest_path.exists() and dest_path.is_dir()
if not dest_path_ok:
warn(
f"Destination directory {dest_path.resolve()} does not exist or is not a directory\n.",
"Configuration files can not be saved",
f'Destination directory {dest_path.resolve()} does not exist or is not a directory\n.',
'Configuration files can not be saved',
)
return dest_path_ok
@ -126,36 +126,36 @@ class ConfigurationOutput:
def save_config(config: ArchConfig) -> None:
def preview(item: MenuItem) -> str | None:
match item.value:
case "user_config":
case 'user_config':
serialized = config_output.user_config_to_json()
return f"{config_output.user_configuration_file}\n{serialized}"
case "user_creds":
return f'{config_output.user_configuration_file}\n{serialized}'
case 'user_creds':
if maybe_serial := config_output.user_credentials_to_json():
return f"{config_output.user_credentials_file}\n{maybe_serial}"
return tr("No configuration")
case "all":
return f'{config_output.user_credentials_file}\n{maybe_serial}'
return tr('No configuration')
case 'all':
output = [str(config_output.user_configuration_file)]
config_output.user_credentials_to_json()
output.append(str(config_output.user_credentials_file))
return "\n".join(output)
return '\n'.join(output)
return None
config_output = ConfigurationOutput(config)
items = [
MenuItem(
tr("Save user configuration (including disk layout)"),
value="user_config",
tr('Save user configuration (including disk layout)'),
value='user_config',
preview_action=preview,
),
MenuItem(
tr("Save user credentials"),
value="user_creds",
tr('Save user credentials'),
value='user_creds',
preview_action=preview,
),
MenuItem(
tr("Save all"),
value="all",
tr('Save all'),
value='all',
preview_action=preview,
),
]
@ -164,8 +164,8 @@ def save_config(config: ArchConfig) -> None:
result = SelectMenu[str](
group,
allow_skip=True,
preview_frame=FrameProperties.max(tr("Configuration")),
preview_size="auto",
preview_frame=FrameProperties.max(tr('Configuration')),
preview_size='auto',
preview_style=PreviewStyle.RIGHT,
).run()
@ -175,21 +175,21 @@ def save_config(config: ArchConfig) -> None:
case ResultType.Selection:
save_option = result.get_value()
case _:
raise ValueError("Unhandled return type")
raise ValueError('Unhandled return type')
readline.set_completer_delims("\t\n=")
readline.parse_and_bind("tab: complete")
readline.set_completer_delims('\t\n=')
readline.parse_and_bind('tab: complete')
dest_path = prompt_dir(
tr("Directory"),
tr("Enter a directory for the configuration(s) to be saved (tab completion enabled)") + "\n",
tr('Directory'),
tr('Enter a directory for the configuration(s) to be saved (tab completion enabled)') + '\n',
allow_skip=True,
)
if not dest_path:
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.focus_item = MenuItem.yes()
@ -208,9 +208,9 @@ def save_config(config: ArchConfig) -> None:
if result.item() == MenuItem.no():
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.focus_item = MenuItem.no()
@ -229,7 +229,7 @@ def save_config(config: ArchConfig) -> None:
case ResultType.Selection:
if result.item() == MenuItem.yes():
password = get_password(
text=tr("Credentials file encryption password"),
text=tr('Credentials file encryption password'),
allow_skip=True,
)
@ -237,9 +237,9 @@ def save_config(config: ArchConfig) -> None:
enc_password = password.plaintext
match save_option:
case "user_config":
case 'user_config':
config_output.save_user_config(dest_path)
case "user_creds":
case 'user_creds':
config_output.save_user_creds(dest_path, password=enc_password)
case "all":
case 'all':
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
libcrypt = ctypes.CDLL("libcrypt.so")
libcrypt = ctypes.CDLL('libcrypt.so')
libcrypt.crypt.argtypes = [ctypes.c_char_p, 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.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:
defs = LOGIN_DEFS.read_text()
for line in defs.split("\n"):
for line in defs.split('\n'):
line = line.strip()
if line.startswith("#"):
if line.startswith('#'):
continue
if line.startswith(key):
value = line.split(" ")[1]
value = line.split(' ')[1]
return value
return None
@ -36,12 +36,12 @@ def _search_login_defs(key: str) -> str | None:
def crypt_gen_salt(prefix: str | bytes, rounds: int) -> bytes:
if isinstance(prefix, str):
prefix = prefix.encode("utf-8")
prefix = prefix.encode('utf-8')
setting = libcrypt.crypt_gensalt(prefix, rounds, None, 0)
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
@ -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
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:
rounds = int(value)
if rounds < 3:
@ -63,17 +63,17 @@ def crypt_yescrypt(plaintext: str) -> str:
else:
rounds = 5
debug(f"Creating yescrypt hash with rounds {rounds}")
debug(f'Creating yescrypt hash with rounds {rounds}')
enc_plaintext = plaintext.encode("utf-8")
salt = crypt_gen_salt("$y$", rounds)
enc_plaintext = plaintext.encode('utf-8')
salt = crypt_gen_salt('$y$', rounds)
crypt_hash = libcrypt.crypt(enc_plaintext, salt)
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:
@ -90,7 +90,7 @@ def _get_fernet(salt: bytes, password: str) -> Fernet:
key = base64.urlsafe_b64encode(
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:
salt = os.urandom(16)
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_salt = base64.urlsafe_b64encode(salt).decode("utf-8")
encoded_token = base64.urlsafe_b64encode(token).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:
_, algo, encoded_salt, encoded_token = data.split("$")
_, algo, encoded_salt, encoded_token = data.split('$')
salt = base64.urlsafe_b64decode(encoded_salt)
token = base64.urlsafe_b64decode(encoded_token)
if algo != "argon2id":
raise ValueError(f"Unsupported algorithm {algo!r}")
if algo != 'argon2id':
raise ValueError(f'Unsupported algorithm {algo!r}')
f = _get_fernet(salt, password)
try:
decrypted = f.decrypt(token)
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:
_TMP_BTRFS_MOUNT = Path("/mnt/arch_btrfs")
_TMP_BTRFS_MOUNT = Path('/mnt/arch_btrfs')
def __init__(self) -> None:
self._devices: dict[Path, BDevice] = {}
@ -73,16 +73,16 @@ class DeviceHandler:
devices = getAllDevices()
devices.extend(self.get_loop_devices())
archiso_mountpoint = Path("/run/archiso/airootfs")
archiso_mountpoint = Path('/run/archiso/airootfs')
for device in devices:
dev_lsblk_info = find_lsblk_info(device.path, all_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
if dev_lsblk_info.type == "rom":
if dev_lsblk_info.type == 'rom':
continue
# exclude archiso loop device
@ -95,7 +95,7 @@ class DeviceHandler:
else:
disk = freshDisk(device, self.partition_table.value)
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
device_info = _DeviceInfo.from_disk(disk)
@ -105,7 +105,7 @@ class DeviceHandler:
lsblk_info = find_lsblk_info(partition.path, dev_lsblk_info.children)
if not lsblk_info:
debug(f"Partition lsblk info not found: {partition.path}")
debug(f'Partition lsblk info not found: {partition.path}')
continue
fs_type = self._determine_fs_type(partition, lsblk_info)
@ -133,20 +133,20 @@ class DeviceHandler:
devices = []
try:
loop_devices = SysCommand(["losetup", "-a"])
loop_devices = SysCommand(['losetup', '-a'])
except SysCallError as err:
debug(f"Failed to get loop devices: {err}")
debug(f'Failed to get loop devices: {err}')
else:
for ld_info in str(loop_devices).splitlines():
try:
loop_device_path, _ = ld_info.split(":", maxsplit=1)
loop_device_path, _ = ld_info.split(':', maxsplit=1)
except ValueError:
continue
try:
loop_device = getDevice(loop_device_path)
except IOException as err:
debug(f"Failed to get loop device: {err}")
debug(f'Failed to get loop device: {err}')
else:
devices.append(loop_device)
@ -166,7 +166,7 @@ class DeviceHandler:
return FilesystemType(lsblk_info.fstype) if lsblk_info.fstype else None
return None
except ValueError:
debug(f"Could not determine the filesystem: {partition.fileSystem}")
debug(f'Could not determine the filesystem: {partition.fileSystem}')
return None
@ -189,12 +189,12 @@ class DeviceHandler:
def get_parent_device_path(self, dev_path: Path) -> 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:
paths = Path("/dev/disk/by-id").glob("*")
paths = Path('/dev/disk/by-id').glob('*')
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:
return linked_wwn_targets[dev_path]
@ -234,9 +234,9 @@ class DeviceHandler:
mountpoint = Path(common_path)
try:
result = SysCommand(f"btrfs subvolume list {mountpoint}").decode()
result = SysCommand(f'btrfs subvolume list {mountpoint}').decode()
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
# It is assumed that lsblk will contain the fields as
@ -250,8 +250,8 @@ class DeviceHandler:
for line in result.splitlines():
# expected output format:
# ID 257 gen 8 top level 5 path @home
name = Path(line.split(" ")[-1])
sub_vol_mountpoint = btrfs_subvol_info.get("/" / name, None)
name = Path(line.split(' ')[-1])
sub_vol_mountpoint = btrfs_subvol_info.get('/' / name, None)
subvol_infos.append(_BtrfsSubvolumeInfo(name, sub_vol_mountpoint))
if not lsblk_info.mountpoint:
@ -272,33 +272,33 @@ class DeviceHandler:
match fs_type:
case FilesystemType.Btrfs | FilesystemType.F2fs | FilesystemType.Xfs:
# Force overwrite
options.append("-f")
options.append('-f')
case FilesystemType.Ext2 | FilesystemType.Ext3 | FilesystemType.Ext4:
# Force create
options.append("-F")
options.append('-F')
case FilesystemType.Fat12 | FilesystemType.Fat16 | FilesystemType.Fat32:
mkfs_type = "fat"
mkfs_type = 'fat'
# Set FAT size
options.extend(("-F", fs_type.value.removeprefix(mkfs_type)))
options.extend(('-F', fs_type.value.removeprefix(mkfs_type)))
case FilesystemType.Ntfs:
# Skip zeroing and bad sector check
options.append("--fast")
options.append('--fast')
case FilesystemType.LinuxSwap:
command = "mkswap"
command = 'mkswap'
case _:
raise UnknownFilesystemFormat(f'Filetype "{fs_type.value}" is not supported')
if not command:
command = f"mkfs.{mkfs_type}"
command = f'mkfs.{mkfs_type}'
cmd = [command, *options, *additional_parted_options, str(path)]
debug("Formatting filesystem:", " ".join(cmd))
debug('Formatting filesystem:', ' '.join(cmd))
try:
SysCommand(cmd)
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)
raise DiskError(msg) from err
@ -322,10 +322,10 @@ class DeviceHandler:
luks_handler.unlock(key_file=key_file)
if not luks_handler.mapper_dev:
raise DiskError("Failed to unlock luks device")
raise DiskError('Failed to unlock luks device')
if lock_after_create:
debug(f"luks2 locking device: {dev_path}")
debug(f'luks2 locking device: {dev_path}')
luks_handler.lock()
return luks_handler
@ -338,7 +338,7 @@ class DeviceHandler:
enc_conf: DiskEncryption,
) -> None:
if not enc_conf.encryption_password:
raise ValueError("No encryption password provided")
raise ValueError('No encryption password provided')
luks_handler = Luks2(
dev_path,
@ -353,69 +353,69 @@ class DeviceHandler:
luks_handler.unlock(key_file=key_file)
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)
info(f"luks2 locking device: {dev_path}")
info(f'luks2 locking device: {dev_path}')
luks_handler.lock()
def _lvm_info(
self,
cmd: str,
info_type: Literal["lv", "vg", "pvseg"],
info_type: Literal['lv', 'vg', 'pvseg'],
) -> 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
# "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)
for report in reports["report"]:
for report in reports['report']:
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]
match info_type:
case "pvseg":
case 'pvseg':
return LvmPVInfo(
pv_name=Path(entry["pv_name"]),
lv_name=entry["lv_name"],
vg_name=entry["vg_name"],
pv_name=Path(entry['pv_name']),
lv_name=entry['lv_name'],
vg_name=entry['vg_name'],
)
case "lv":
case 'lv':
return LvmVolumeInfo(
lv_name=entry["lv_name"],
vg_name=entry["vg_name"],
lv_size=Size(int(entry["lv_size"][:-1]), Unit.B, SectorSize.default()),
lv_name=entry['lv_name'],
vg_name=entry['vg_name'],
lv_size=Size(int(entry['lv_size'][:-1]), Unit.B, SectorSize.default()),
)
case "vg":
case 'vg':
return LvmGroupInfo(
vg_uuid=entry["vg_uuid"],
vg_size=Size(int(entry["vg_size"][:-1]), Unit.B, SectorSize.default()),
vg_uuid=entry['vg_uuid'],
vg_size=Size(int(entry['vg_size'][:-1]), Unit.B, SectorSize.default()),
)
return None
@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
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
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(
self,
cmd: str,
info_type: Literal["lv", "vg", "pvseg"],
info_type: Literal['lv', 'vg', 'pvseg'],
) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None:
while True:
try:
@ -424,63 +424,63 @@ class DeviceHandler:
time.sleep(3)
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:
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:
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:
active_flag = "y" if activate else "n"
cmd = f"lvchange -a {active_flag} {vol.safe_dev_path}"
active_flag = 'y' if activate else 'n'
cmd = f'lvchange -a {active_flag} {vol.safe_dev_path}'
debug(f"lvchange volume: {cmd}")
debug(f'lvchange volume: {cmd}')
SysCommand(cmd)
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)
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)
def lvm_vol_reduce(self, vol_path: Path, amount: Size) -> None:
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)
def lvm_pv_create(self, pvs: Iterable[Path]) -> None:
cmd = "pvcreate " + " ".join([str(pv) for pv in pvs])
debug(f"Creating LVM PVS: {cmd}")
cmd = 'pvcreate ' + ' '.join([str(pv) for pv in pvs])
debug(f'Creating LVM PVS: {cmd}')
worker = SysCommandWorker(cmd)
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:
pvs_str = " ".join([str(pv) for pv in pvs])
cmd = f"vgcreate --yes {vg_name} {pvs_str}"
pvs_str = ' '.join([str(pv) for pv in pvs])
cmd = f'vgcreate --yes {vg_name} {pvs_str}'
debug(f"Creating LVM group: {cmd}")
debug(f'Creating LVM group: {cmd}')
worker = SysCommandWorker(cmd)
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:
if offset is not None:
@ -489,16 +489,16 @@ class DeviceHandler:
length = volume.length
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.poll()
worker.write(b"y\n", line_ending=False)
worker.write(b'y\n', line_ending=False)
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(
self,
@ -510,11 +510,11 @@ class DeviceHandler:
# when we require a delete and the partition to be (re)created
# already exists then we have to delete it first
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)
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)
@ -550,14 +550,14 @@ class DeviceHandler:
for flag in part_mod.flags:
partition.setFlag(flag.flag_id)
debug(f"\tType: {part_mod.type.value}")
debug(f"\tFilesystem: {fs_value}")
debug(f"\tGeometry: {start_sector.value} start sector, {length_sector.value} length")
debug(f'\tType: {part_mod.type.value}')
debug(f'\tFilesystem: {fs_value}')
debug(f'\tGeometry: {start_sector.value} start sector, {length_sector.value} length')
try:
disk.addPartition(partition=partition, constraint=disk.device.optimalAlignedConstraint)
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 part_mod.is_root():
@ -572,18 +572,18 @@ class DeviceHandler:
lsblk_info = get_lsblk_info(path)
if not lsblk_info.partn:
debug(f"Unable to determine new partition number: {path}\n{lsblk_info}")
raise DiskError(f"Unable to determine new partition number: {path}")
debug(f'Unable to determine new partition number: {path}\n{lsblk_info}')
raise DiskError(f'Unable to determine new partition number: {path}')
if not lsblk_info.partuuid:
debug(f"Unable to determine new partition uuid: {path}\n{lsblk_info}")
raise DiskError(f"Unable to determine new partition uuid: {path}")
debug(f'Unable to determine new partition uuid: {path}\n{lsblk_info}')
raise DiskError(f'Unable to determine new partition uuid: {path}')
if not lsblk_info.uuid:
debug(f"Unable to determine new uuid: {path}\n{lsblk_info}")
raise DiskError(f"Unable to determine new uuid: {path}")
debug(f'Unable to determine new uuid: {path}\n{lsblk_info}')
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
@ -593,28 +593,28 @@ class DeviceHandler:
btrfs_subvols: list[SubvolumeModification],
mount_options: list[str],
) -> None:
info(f"Creating subvolumes: {path}")
info(f'Creating subvolumes: {path}')
self.mount(path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True)
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
SysCommand(f"btrfs subvolume create -p {subvol_path}")
SysCommand(f'btrfs subvolume create -p {subvol_path}')
if BtrfsMountOption.nodatacow.value in mount_options:
try:
SysCommand(f"chattr +C {subvol_path}")
SysCommand(f'chattr +C {subvol_path}')
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:
try:
SysCommand(f"chattr +c {subvol_path}")
SysCommand(f'chattr +c {subvol_path}')
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)
@ -623,12 +623,12 @@ class DeviceHandler:
part_mod: PartitionModification,
enc_conf: DiskEncryption | 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
if enc_conf is not None and part_mod in enc_conf.partitions:
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(
part_mod.safe_dev_path,
@ -637,7 +637,7 @@ class DeviceHandler:
)
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
else:
@ -652,11 +652,11 @@ class DeviceHandler:
)
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
SysCommand(f"btrfs subvolume create -p {subvol_path}")
SysCommand(f'btrfs subvolume create -p {subvol_path}')
umount(dev_path)
@ -675,17 +675,17 @@ class DeviceHandler:
luks_handler.unlock()
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
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
for partition in existing_partitions:
debug(f"Unmounting: {partition.path}")
debug(f'Unmounting: {partition.path}')
# un-mount for existing encrypted partitions
if partition.fs_type == FilesystemType.Crypto_luks:
@ -706,15 +706,15 @@ class DeviceHandler:
# WARNING: the entire device will be wiped and all data lost
if modification.wipe:
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)
disk = freshDisk(modification.device.disk.device, partition_table.value)
else:
info(f"Use existing device: {modification.device_path}")
info(f'Use existing device: {modification.device_path}')
disk = modification.device.disk
info(f"Creating partitions: {modification.device_path}")
info(f'Creating partitions: {modification.device_path}')
# don't touch existing partitions
filtered_part = [p for p in modification.partitions if not p.exists()]
@ -730,9 +730,9 @@ class DeviceHandler:
@staticmethod
def swapon(path: Path) -> None:
try:
SysCommand(["swapon", str(path)])
SysCommand(['swapon', str(path)])
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(
self,
@ -746,30 +746,30 @@ class DeviceHandler:
target_mountpoint.mkdir(parents=True, exist_ok=True)
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)
if target_mountpoint in lsblk_info.mountpoints:
info(f"Device already mounted at {target_mountpoint}")
info(f'Device already mounted at {target_mountpoint}')
return
cmd = ["mount"]
cmd = ['mount']
if len(options):
cmd.extend(("-o", ",".join(options)))
cmd.extend(('-o', ','.join(options)))
if mount_fs:
cmd.extend(("-t", mount_fs))
cmd.extend(('-t', mount_fs))
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:
SysCommand(command)
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]:
part_mods: dict[Path, list[PartitionModification]] = {}
@ -799,16 +799,16 @@ class DeviceHandler:
def partprobe(self, path: Path | None = None) -> None:
if path is not None:
command = f"partprobe {path}"
command = f'partprobe {path}'
else:
command = "partprobe"
command = 'partprobe'
try:
debug(f"Calling partprobe: {command}")
debug(f'Calling partprobe: {command}')
SysCommand(command)
except SysCallError as 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)
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)
else:
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.
@type dev_path: str
"""
with open(dev_path, "wb") as p:
with open(dev_path, 'wb') as p:
p.write(bytearray(1024))
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
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:
luks = Luks2(partition.path)
@ -841,9 +841,9 @@ class DeviceHandler:
@staticmethod
def udev_sync() -> None:
try:
SysCommand("udevadm settle")
SysCommand('udevadm settle')
except SysCallError as err:
debug(f"Failed to synchronize with udev: {err}")
debug(f'Failed to synchronize with udev: {err}')
device_handler = DeviceHandler()

View File

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

View File

@ -50,43 +50,43 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
def _define_menu_options(self) -> list[MenuItem]:
return [
MenuItem(
text=tr("Encryption type"),
text=tr('Encryption type'),
action=lambda x: select_encryption_type(self._disk_config, x),
value=self._enc_config.encryption_type,
preview_action=self._preview,
key="encryption_type",
key='encryption_type',
),
MenuItem(
text=tr("Encryption password"),
text=tr('Encryption password'),
action=lambda x: select_encrypted_password(),
value=self._enc_config.encryption_password,
dependencies=[self._check_dep_enc_type],
preview_action=self._preview,
key="encryption_password",
key='encryption_password',
),
MenuItem(
text=tr("Partitions"),
text=tr('Partitions'),
action=lambda x: select_partitions_to_encrypt(self._disk_config.device_modifications, x),
value=self._enc_config.partitions,
dependencies=[self._check_dep_partitions],
preview_action=self._preview,
key="partitions",
key='partitions',
),
MenuItem(
text=tr("LVM volumes"),
text=tr('LVM volumes'),
action=self._select_lvm_vols,
value=self._enc_config.lvm_volumes,
dependencies=[self._check_dep_lvm_vols],
preview_action=self._preview,
key="lvm_volumes",
key='lvm_volumes',
),
MenuItem(
text=tr("HSM"),
text=tr('HSM'),
action=select_hsm,
value=self._enc_config.hsm_device,
dependencies=[self._check_dep_enc_type],
preview_action=self._preview,
key="hsm_device",
key='hsm_device',
),
]
@ -96,19 +96,19 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
return []
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:
return True
return False
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]:
return True
return False
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:
return True
return False
@ -117,10 +117,10 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
def run(self) -> DiskEncryption | None:
super().run()
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_partitions = self._item_group.find_by_key("partitions").value
enc_lvm_vols = self._item_group.find_by_key("lvm_volumes").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_partitions = self._item_group.find_by_key('partitions').value
enc_lvm_vols = self._item_group.find_by_key('lvm_volumes').value
assert enc_type is not None
assert enc_partitions is not None
@ -144,22 +144,22 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
return None
def _preview(self, item: MenuItem) -> str | None:
output = ""
output = ''
if (enc_type := self._prev_type()) is not None:
output += enc_type
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:
output += f"\n{fido_device}"
output += f'\n{fido_device}'
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:
output += f"\n\n{lvm}"
output += f'\n\n{lvm}'
if not output:
return None
@ -167,51 +167,51 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
return output
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:
enc_text = EncryptionType.type_to_text(enc_type)
return f"{tr('Encryption type')}: {enc_text}"
return f'{tr("Encryption type")}: {enc_text}'
return 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:
return f"{tr('Encryption password')}: {enc_pwd.hidden()}"
return f'{tr("Encryption password")}: {enc_pwd.hidden()}'
return 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:
output = tr("Partitions to be encrypted") + "\n"
output = tr('Partitions to be encrypted') + '\n'
output += FormattedOutput.as_table(partitions)
return output.rstrip()
return 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:
output = tr("LVM volumes to be encrypted") + "\n"
output = tr('LVM volumes to be encrypted') + '\n'
output += FormattedOutput.as_table(volumes)
return output.rstrip()
return 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:
return None
output = str(fido_device.path)
output += f" ({fido_device.manufacturer}, {fido_device.product})"
return f"{tr('HSM device')}: {output}"
output += f' ({fido_device.manufacturer}, {fido_device.product})'
return f'{tr("HSM device")}: {output}'
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_reset=True,
alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Encryption type")),
frame=FrameProperties.min(tr('Encryption type')),
).run()
match result.type_:
@ -245,9 +245,9 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt
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(
text=tr("Disk encryption password"),
text=tr('Disk encryption password'),
header=header,
allow_skip=True,
)
@ -256,7 +256,7 @@ def select_encrypted_password() -> Password | 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:
fido_devices = Fido2.get_fido2_devices()
@ -292,7 +292,7 @@ def select_partitions_to_encrypt(
# do not allow encrypting the boot partition
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
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
if not cls._loaded or reload:
try:
ret = SysCommand("systemd-cryptenroll --fido2-device=list").decode()
ret = SysCommand('systemd-cryptenroll --fido2-device=list').decode()
except SysCallError:
error("fido2 support is most likely not installed")
raise ValueError("HSM devices can not be detected, is libfido2 installed?")
error('fido2 support is most likely not installed')
raise ValueError('HSM devices can not be detected, is libfido2 installed?')
fido_devices = clear_vt100_escape_codes_from_str(ret)
@ -51,10 +51,10 @@ class Fido2:
product_pos = 0
devices = []
for line in fido_devices.split("\r\n"):
if "/dev" not in line:
manufacturer_pos = line.find("MANUFACTURER")
product_pos = line.find("PRODUCT")
for line in fido_devices.split('\r\n'):
if '/dev' not in line:
manufacturer_pos = line.find('MANUFACTURER')
product_pos = line.find('PRODUCT')
continue
path = line[:manufacturer_pos].rstrip()
@ -77,18 +77,18 @@ class Fido2:
dev_path: Path,
password: Password,
) -> 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
pin_inputted = False
while worker.is_alive():
if pw_inputted is False:
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"))
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'))
pw_inputted = True
elif pin_inputted is False:
if bytes("please enter security token pin", "UTF-8") in worker._trace_log.lower():
worker.write(bytes(getpass.getpass(" "), "UTF-8"))
if bytes('please enter security token pin', 'UTF-8') in worker._trace_log.lower():
worker.write(bytes(getpass.getpass(' '), 'UTF-8'))
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:
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
device_mods = [d for d in self._disk_config.device_modifications if d.partitions]
if not device_mods:
debug("No modifications required")
debug('No modifications required')
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:
self._final_warning(device_paths)
@ -66,7 +66,7 @@ class FilesystemHandler:
if self._disk_config.lvm_config:
for mod in device_mods:
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(
[boot_part],
mod.device_path,
@ -123,11 +123,11 @@ class FilesystemHandler:
def _validate_partitions(self, partitions: list[PartitionModification]) -> None:
checks = {
# 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
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
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():
@ -136,7 +136,7 @@ class FilesystemHandler:
raise exc
def perform_lvm_operations(self) -> None:
info("Setting up LVM config...")
info('Setting up LVM config...')
if not self._disk_config.lvm_config:
return
@ -195,7 +195,7 @@ class FilesystemHandler:
vg_info = device_handler.lvm_group_info(vg.name)
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
# total PVs size due to reserved metadata storage etc.
@ -213,11 +213,11 @@ class FilesystemHandler:
for lv in vg.volumes:
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)
while True:
debug("Fetching LVM volume info")
debug('Fetching LVM volume info')
lv_info = device_handler.lvm_vol_info(lv.name)
if lv_info is not None:
break
@ -234,7 +234,7 @@ class FilesystemHandler:
for vol in lvm_config.get_all_volumes():
if enc_vol := enc_vols.get(vol, None):
if not enc_vol.mapper_dev:
raise ValueError("No mapper device defined")
raise ValueError('No mapper device defined')
path = enc_vol.mapper_dev
else:
path = vol.safe_dev_path
@ -340,13 +340,13 @@ class FilesystemHandler:
def _final_warning(self, device_paths: str) -> bool:
# Issue a final warning before we continue with something un-revertable.
# We mention the drive one last time, and count from 5 to 0.
out = tr(" ! Formatting {} in ").format(device_paths)
Tui.print(out, row=0, endl="", clear_screen=True)
out = tr(' ! Formatting {} in ').format(device_paths)
Tui.print(out, row=0, endl='', clear_screen=True)
try:
countdown = "\n5...4...3...2...1\n"
countdown = '\n5...4...3...2...1\n'
for c in countdown:
Tui.print(c, row=0, endl="")
Tui.print(c, row=0, endl='')
time.sleep(0.25)
except KeyboardInterrupt:
with Tui():

View File

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

View File

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

View File

@ -17,13 +17,13 @@ def _fetch_lsblk_info(
reverse: bool = False,
full_dev_path: bool = False,
) -> LsblkOutput:
cmd = ["lsblk", "--json", "--bytes", "--output", ",".join(LsblkInfo.fields())]
cmd = ['lsblk', '--json', '--bytes', '--output', ','.join(LsblkInfo.fields())]
if reverse:
cmd.append("--inverse")
cmd.append('--inverse')
if full_dev_path:
cmd.append("--paths")
cmd.append('--paths')
if dev_path:
cmd.append(str(dev_path))
@ -33,7 +33,7 @@ def _fetch_lsblk_info(
except SysCallError as err:
# Get the output minus the message/info from lsblk if it returns a non-zero exit code.
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:
raise DiskError(f'Failed to read disk "{dev_path}" with lsblk')
@ -104,8 +104,8 @@ def disk_layouts() -> str:
try:
lsblk_output = get_lsblk_output()
except SysCallError as err:
warn(f"Could not return disk layouts: {err}")
return ""
warn(f'Could not return disk layouts: {err}')
return ''
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:
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:
cmd.append("-R")
cmd.append('-R')
for path in lsblk_info.mountpoints:
debug(f"Unmounting mountpoint: {path}")
debug(f'Unmounting mountpoint: {path}')
SysCommand(cmd + [str(path)])

View File

@ -11,7 +11,7 @@ class UnknownFilesystemFormat(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)
self.message = message
self.exit_code = exit_code

View File

@ -23,27 +23,27 @@ from .output import debug, error
from .storage import storage
# 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()
def generate_password(length: int = 64) -> str:
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:
if path := which(name):
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:
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:
return re.sub(_VT100_ESCAPE_REGEX, "", data)
return re.sub(_VT100_ESCAPE_REGEX, '', data)
def jsonify(obj: object, safe: bool = True) -> object:
@ -57,11 +57,11 @@ def jsonify(obj: object, safe: bool = True) -> object:
return {
key: jsonify(value, safe)
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):
return obj.value
if hasattr(obj, "json"):
if hasattr(obj, 'json'):
# json() is a friendly name for json-helper, it should return
# a dictionary representation of the object so that it can be
# 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]
if isinstance(obj, Path):
return str(obj)
if hasattr(obj, "__dict__"):
if hasattr(obj, '__dict__'):
return vars(obj)
return obj
@ -104,26 +104,26 @@ class SysCommandWorker:
cmd: str | list[str],
peek_output: bool | None = False,
environment_vars: dict[str, str] | None = None,
working_directory: str | None = "./",
working_directory: str | None = './',
remove_vt100_escape_codes_from_lines: bool = True,
):
if isinstance(cmd, str):
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])
self.cmd = cmd
self.peek_output = peek_output
# 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:
self.environment_vars.update(environment_vars)
self.working_directory = working_directory
self.exit_code: int | None = None
self._trace_log = b""
self._trace_log = b''
self._trace_log_pos = 0
self.poll_object = epoll()
self.child_fd: int | None = None
@ -146,13 +146,13 @@ class SysCommandWorker:
return False
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())
for line in lines:
if self.remove_vt100_escape_codes_from_lines:
line = clear_vt100_escape_codes(line)
yield line + b"\n"
yield line + b'\n'
self._trace_log_pos = last_line
@ -164,11 +164,11 @@ class SysCommandWorker:
@override
def __str__(self) -> str:
try:
return self._trace_log.decode("utf-8")
return self._trace_log.decode('utf-8')
except UnicodeDecodeError:
return str(self._trace_log)
def __enter__(self) -> "SysCommandWorker":
def __enter__(self) -> 'SysCommandWorker':
return self
def __exit__(self, *args: str) -> None:
@ -184,7 +184,7 @@ class SysCommandWorker:
if self.peek_output:
# To make sure any peaked output didn't leave us hanging
# on the same line we were on.
sys.stdout.write("\n")
sys.stdout.write('\n')
sys.stdout.flush()
if len(args) >= 2 and args[1]:
@ -192,7 +192,7 @@ class SysCommandWorker:
if self.exit_code != 0:
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,
worker_log=self._trace_log,
)
@ -211,7 +211,7 @@ class SysCommandWorker:
self.make_sure_we_are_executing()
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
@ -233,17 +233,17 @@ class SysCommandWorker:
if self.peek_output:
if isinstance(output, bytes):
try:
output = output.decode("UTF-8")
output = output.decode('UTF-8')
except UnicodeDecodeError:
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
if peak_logfile.exists() is False:
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))
if change_perm:
@ -301,7 +301,7 @@ class SysCommandWorker:
try:
os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars})
except FileNotFoundError:
error(f"{self.cmd[0]} does not exist.")
error(f'{self.cmd[0]} does not exist.')
self.exit_code = 1
return False
else:
@ -313,7 +313,7 @@ class SysCommandWorker:
return True
def decode(self, encoding: str = "UTF-8") -> str:
def decode(self, encoding: str = 'UTF-8') -> str:
return self._trace_log.decode(encoding)
@ -323,7 +323,7 @@ class SysCommand:
cmd: str | list[str],
peek_output: bool | None = False,
environment_vars: dict[str, str] | None = None,
working_directory: str | None = "./",
working_directory: str | None = './',
remove_vt100_escape_codes_from_lines: bool = True,
):
self.cmd = cmd
@ -351,7 +351,7 @@ class SysCommand:
def __getitem__(self, key: slice) -> bytes | None:
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:
start = key.start or 0
end = key.stop or len(self.session._trace_log)
@ -362,7 +362,7 @@ class SysCommand:
@override
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:
"""
@ -386,14 +386,14 @@ class SysCommand:
self.session.poll()
if self.peek_output:
sys.stdout.write("\n")
sys.stdout.write('\n')
sys.stdout.flush()
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:
raise ValueError("No session available to decode")
raise ValueError('No session available to decode')
val = self.session._trace_log.decode(encoding, errors=errors)
@ -403,10 +403,10 @@ class SysCommand:
def output(self, remove_cr: bool = True) -> bytes:
if not self.session:
raise ValueError("No session available")
raise ValueError('No session available')
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
@ -425,15 +425,15 @@ class SysCommand:
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
if history_logfile.exists() is False:
change_perm = True
try:
with history_logfile.open("a") as cmd_log:
cmd_log.write(f"{time.time()} {cmd}\n")
with history_logfile.open('a') as cmd_log:
cmd_log.write(f'{time.time()} {cmd}\n')
if change_perm:
history_logfile.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
@ -459,6 +459,6 @@ def run(
def _pid_exists(pid: int) -> bool:
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:
return False

View File

@ -55,151 +55,151 @@ class GlobalMenu(AbstractMenu[None]):
def _get_menu_options(self) -> list[MenuItem]:
return [
MenuItem(
text=tr("Archinstall language"),
text=tr('Archinstall language'),
action=self._select_archinstall_language,
display_action=lambda x: x.display_name if x else "",
key="archinstall_language",
display_action=lambda x: x.display_name if x else '',
key='archinstall_language',
),
MenuItem(
text=tr("Locales"),
text=tr('Locales'),
action=self._locale_selection,
preview_action=self._prev_locale,
key="locale_config",
key='locale_config',
),
MenuItem(
text=tr("Mirrors and repositories"),
text=tr('Mirrors and repositories'),
action=self._mirror_configuration,
preview_action=self._prev_mirror_config,
key="mirror_config",
key='mirror_config',
),
MenuItem(
text=tr("Disk configuration"),
text=tr('Disk configuration'),
action=self._select_disk_config,
preview_action=self._prev_disk_config,
mandatory=True,
key="disk_config",
key='disk_config',
),
MenuItem(
text=tr("Disk encryption"),
text=tr('Disk encryption'),
action=self._disk_encryption,
preview_action=self._prev_disk_encryption,
dependencies=["disk_config"],
key="disk_encryption",
dependencies=['disk_config'],
key='disk_encryption',
),
MenuItem(
text=tr("Swap"),
text=tr('Swap'),
value=True,
action=ask_for_swap,
preview_action=self._prev_swap,
key="swap",
key='swap',
),
MenuItem(
text=tr("Bootloader"),
text=tr('Bootloader'),
value=Bootloader.get_default(),
action=self._select_bootloader,
preview_action=self._prev_bootloader,
mandatory=True,
key="bootloader",
key='bootloader',
),
MenuItem(
text=tr("Unified kernel images"),
text=tr('Unified kernel images'),
value=False,
enabled=SysInfo.has_uefi(),
action=ask_for_uki,
preview_action=self._prev_uki,
key="uki",
key='uki',
),
MenuItem(
text=tr("Hostname"),
value="archlinux",
text=tr('Hostname'),
value='archlinux',
action=ask_hostname,
preview_action=self._prev_hostname,
key="hostname",
key='hostname',
),
MenuItem(
text=tr("Root password"),
text=tr('Root password'),
action=self._set_root_password,
preview_action=self._prev_root_pwd,
key="root_enc_password",
key='root_enc_password',
),
MenuItem(
text=tr("User account"),
text=tr('User account'),
action=self._create_user_account,
preview_action=self._prev_users,
key="users",
key='users',
),
MenuItem(
text=tr("Profile"),
text=tr('Profile'),
action=self._select_profile,
preview_action=self._prev_profile,
key="profile_config",
key='profile_config',
),
MenuItem(
text=tr("Audio"),
text=tr('Audio'),
action=ask_for_audio_selection,
preview_action=self._prev_audio,
key="audio_config",
key='audio_config',
),
MenuItem(
text=tr("Kernels"),
value=["linux"],
text=tr('Kernels'),
value=['linux'],
action=select_kernel,
preview_action=self._prev_kernel,
mandatory=True,
key="kernels",
key='kernels',
),
MenuItem(
text=tr("Network configuration"),
text=tr('Network configuration'),
action=ask_to_configure_network,
value={},
preview_action=self._prev_network_config,
key="network_config",
key='network_config',
),
MenuItem(
text=tr("Parallel Downloads"),
text=tr('Parallel Downloads'),
action=add_number_of_parallel_downloads,
value=0,
preview_action=self._prev_parallel_dw,
key="parallel_downloads",
key='parallel_downloads',
),
MenuItem(
text=tr("Additional packages"),
text=tr('Additional packages'),
action=self._select_additional_packages,
value=[],
preview_action=self._prev_additional_pkgs,
key="packages",
key='packages',
),
MenuItem(
text=tr("Timezone"),
text=tr('Timezone'),
action=ask_for_a_timezone,
value="UTC",
value='UTC',
preview_action=self._prev_tz,
key="timezone",
key='timezone',
),
MenuItem(
text=tr("Automatic time sync (NTP)"),
text=tr('Automatic time sync (NTP)'),
action=ask_ntp,
value=True,
preview_action=self._prev_ntp,
key="ntp",
key='ntp',
),
MenuItem(
text="",
text='',
),
MenuItem(
text=tr("Save configuration"),
text=tr('Save configuration'),
action=lambda x: self._safe_config(),
key=f"{CONFIG_KEY}_save",
key=f'{CONFIG_KEY}_save',
),
MenuItem(
text=tr("Install"),
text=tr('Install'),
preview_action=self._prev_install_invalid_config,
key=f"{CONFIG_KEY}_install",
key=f'{CONFIG_KEY}_install',
),
MenuItem(
text=tr("Abort"),
text=tr('Abort'),
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()
def has_superuser() -> bool:
item = self._item_group.find_by_key("users")
item = self._item_group.find_by_key('users')
if item.has_value():
users = item.value
@ -229,10 +229,10 @@ class GlobalMenu(AbstractMenu[None]):
missing = set()
for item in self._item_group.items:
if item.key in ["root_enc_password", "users"]:
if not check("root_enc_password") and not has_superuser():
if item.key in ['root_enc_password', 'users']:
if not check('root_enc_password') and not has_superuser():
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:
if not check(item.key):
@ -271,11 +271,11 @@ class GlobalMenu(AbstractMenu[None]):
self._item_group.find_by_key(o.key).text = o.text
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:
# 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):
return None
@ -300,26 +300,26 @@ class GlobalMenu(AbstractMenu[None]):
if network_config.type == NicType.MANUAL:
output = FormattedOutput.as_table(network_config.nics)
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 None
def _prev_additional_pkgs(self, item: MenuItem) -> str | None:
if item.value:
output = "\n".join(sorted(item.value))
output = '\n'.join(sorted(item.value))
return output
return None
def _prev_tz(self, item: MenuItem) -> str | None:
if item.value:
return f"{tr('Timezone')}: {item.value}"
return f'{tr("Timezone")}: {item.value}'
return None
def _prev_ntp(self, item: MenuItem) -> str | None:
if item.value is not None:
output = f"{tr('NTP')}: "
output += tr("Enabled") if item.value else tr("Disabled")
output = f'{tr("NTP")}: '
output += tr('Enabled') if item.value else tr('Disabled')
return output
return None
@ -327,13 +327,13 @@ class GlobalMenu(AbstractMenu[None]):
disk_layout_conf: DiskLayoutConfiguration | None = item.value
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:
output += tr("Mountpoint") + ": " + str(disk_layout_conf.mountpoint)
output += tr('Mountpoint') + ': ' + str(disk_layout_conf.mountpoint)
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
@ -341,72 +341,72 @@ class GlobalMenu(AbstractMenu[None]):
def _prev_swap(self, item: MenuItem) -> str | None:
if item.value is not None:
output = f"{tr('Swap on zram')}: "
output += tr("Enabled") if item.value else tr("Disabled")
output = f'{tr("Swap on zram")}: '
output += tr('Enabled') if item.value else tr('Disabled')
return output
return None
def _prev_uki(self, item: MenuItem) -> str | None:
if item.value is not None:
output = f"{tr('Unified kernel images')}: "
output += tr("Enabled") if item.value else tr("Disabled")
output = f'{tr("Unified kernel images")}: '
output += tr('Enabled') if item.value else tr('Disabled')
return output
return None
def _prev_hostname(self, item: MenuItem) -> str | None:
if item.value is not None:
return f"{tr('Hostname')}: {item.value}"
return f'{tr("Hostname")}: {item.value}'
return None
def _prev_root_pwd(self, item: MenuItem) -> str | None:
if item.value is not None:
password: Password = item.value
return f"{tr('Root password')}: {password.hidden()}"
return f'{tr("Root password")}: {password.hidden()}'
return None
def _prev_audio(self, item: MenuItem) -> str | None:
if item.value is not None:
config: AudioConfiguration = item.value
return f"{tr('Audio')}: {config.audio.value}"
return f'{tr("Audio")}: {config.audio.value}'
return None
def _prev_parallel_dw(self, item: MenuItem) -> str | None:
if item.value is not None:
return f"{tr('Parallel Downloads')}: {item.value}"
return f'{tr("Parallel Downloads")}: {item.value}'
return None
def _prev_kernel(self, item: MenuItem) -> str | None:
if item.value:
kernel = ", ".join(item.value)
return f"{tr('Kernel')}: {kernel}"
kernel = ', '.join(item.value)
return f'{tr("Kernel")}: {kernel}'
return None
def _prev_bootloader(self, item: MenuItem) -> str | None:
if item.value is not None:
return f"{tr('Bootloader')}: {item.value.value}"
return f'{tr("Bootloader")}: {item.value.value}'
return 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
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:
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:
output += tr("Password") + f": {enc_config.encryption_password.hidden()}\n"
output += tr('Password') + f': {enc_config.encryption_password.hidden()}\n'
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:
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:
output += f"HSM: {enc_config.hsm_device.manufacturer}"
output += f'HSM: {enc_config.hsm_device.manufacturer}'
return output
@ -423,12 +423,12 @@ class GlobalMenu(AbstractMenu[None]):
XXX: The caller is responsible for wrapping the string with the translation
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
boot_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:
if root_partition := layout.get_root_partition():
break
@ -440,36 +440,36 @@ class GlobalMenu(AbstractMenu[None]):
if efi_partition := layout.get_efi_partition():
break
else:
return "No disk layout selected"
return 'No disk layout selected'
if root_partition is None:
return "Root partition not found"
return 'Root partition not found'
if boot_partition is None:
return "Boot partition not found"
return 'Boot partition not found'
if SysInfo.has_uefi():
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]:
return "ESP must be formatted as a FAT filesystem"
return 'ESP must be formatted as a FAT filesystem'
if bootloader == Bootloader.Limine:
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
def _prev_install_invalid_config(self, item: MenuItem) -> str | None:
if missing := self._missing_configs():
text = tr("Missing configurations:\n")
text = tr('Missing configurations:\n')
for m in missing:
text += f"- {m}\n"
text += f'- {m}\n'
return text[:-1] # remove last new line
if error := self._validate_bootloader():
return tr(f"Invalid configuration: {error}")
return tr(f'Invalid configuration: {error}')
return None
@ -484,24 +484,24 @@ class GlobalMenu(AbstractMenu[None]):
profile_config: ProfileConfiguration | None = item.value
if profile_config and profile_config.profile:
output = tr("Profiles") + ": "
output = tr('Profiles') + ': '
if profile_names := profile_config.profile.current_selection_names():
output += ", ".join(profile_names) + "\n"
output += ', '.join(profile_names) + '\n'
else:
output += profile_config.profile.name + "\n"
output += profile_config.profile.name + '\n'
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:
output += tr("Greeter") + ": " + profile_config.greeter.value + "\n"
output += tr('Greeter') + ': ' + profile_config.greeter.value + '\n'
return output
return 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
def _select_disk_config(
@ -511,7 +511,7 @@ class GlobalMenu(AbstractMenu[None]):
disk_config = DiskLayoutConfigurationMenu(preset).run()
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
@ -519,7 +519,7 @@ class GlobalMenu(AbstractMenu[None]):
bootloader = ask_for_bootloader(preset)
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():
uki.value = False
uki.enabled = False
@ -535,7 +535,7 @@ class GlobalMenu(AbstractMenu[None]):
return profile_config
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()
if config:
@ -574,28 +574,28 @@ class GlobalMenu(AbstractMenu[None]):
mirror_config: MirrorConfiguration = item.value
output = ""
output = ''
if mirror_config.mirror_regions:
title = tr("Selected mirror regions")
divider = "-" * len(title)
title = tr('Selected mirror regions')
divider = '-' * len(title)
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:
title = tr("Custom servers")
divider = "-" * len(title)
title = tr('Custom servers')
divider = '-' * len(title)
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:
title = tr("Optional repositories")
divider = "-" * len(title)
repos = ", ".join([r.value for r in mirror_config.optional_repositories])
output += f"{title}\n{divider}\n{repos}\n\n"
title = tr('Optional repositories')
divider = '-' * len(title)
repos = ', '.join([r.value for r in mirror_config.optional_repositories])
output += f'{title}\n{divider}\n{repos}\n\n'
if mirror_config.custom_repositories:
title = tr("Custom repositories")
title = tr('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()

View File

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

View File

@ -63,8 +63,8 @@ def select_devices(preset: list[BDevice] | None = []) -> list[BDevice]:
search_enabled=False,
multi=True,
preview_style=PreviewStyle.BOTTOM,
preview_size="auto",
preview_frame=FrameProperties.max("Partitions"),
preview_size='auto',
preview_frame=FrameProperties.max('Partitions'),
allow_skip=True,
).run()
@ -136,7 +136,7 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
group,
allow_skip=True,
alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Disk configuration type")),
frame=FrameProperties.min(tr('Disk configuration type')),
allow_reset=True,
).run()
@ -149,10 +149,10 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
selection = result.get_value()
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"
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:
return None
@ -206,7 +206,7 @@ def select_lvm_config(
group,
allow_reset=True,
allow_skip=True,
frame=FrameProperties.min(tr("LVM configuration type")),
frame=FrameProperties.min(tr('LVM configuration type')),
alignment=Alignment.CENTER,
).run()
@ -235,7 +235,7 @@ def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModifi
type=PartitionType.Primary,
start=start,
length=size,
mountpoint=Path("/boot"),
mountpoint=Path('/boot'),
fs_type=FilesystemType.Fat32,
flags=flags,
)
@ -243,20 +243,20 @@ def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModifi
def select_main_filesystem_format() -> FilesystemType:
items = [
MenuItem("btrfs", value=FilesystemType.Btrfs),
MenuItem("ext4", value=FilesystemType.Ext4),
MenuItem("xfs", value=FilesystemType.Xfs),
MenuItem("f2fs", value=FilesystemType.F2fs),
MenuItem('btrfs', value=FilesystemType.Btrfs),
MenuItem('ext4', value=FilesystemType.Ext4),
MenuItem('xfs', value=FilesystemType.Xfs),
MenuItem('f2fs', value=FilesystemType.F2fs),
]
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)
result = SelectMenu[FilesystemType](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min("Filesystem"),
frame=FrameProperties.min('Filesystem'),
allow_skip=False,
).run()
@ -264,13 +264,13 @@ def select_main_filesystem_format() -> FilesystemType:
case ResultType.Selection:
return result.get_value()
case _:
raise ValueError("Unhandled result type")
raise ValueError('Unhandled result type')
def select_mount_options() -> list[str]:
prompt = tr("Would you like to use compression or disable CoW?") + "\n"
compression = tr("Use compression")
disable_cow = tr("Disable Copy-on-Write")
prompt = tr('Would you like to use compression or disable CoW?') + '\n'
compression = tr('Use compression')
disable_cow = tr('Disable Copy-on-Write')
items = [
MenuItem(compression, value=BtrfsMountOption.compress.value),
@ -293,7 +293,7 @@ def select_mount_options() -> list[str]:
case ResultType.Selection:
return [result.get_value()]
case _:
raise ValueError("Unhandled result type")
raise ValueError('Unhandled result type')
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)
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.set_focus_by_value(MenuItem.yes().value)
result = SelectMenu[bool](
@ -362,7 +362,7 @@ def suggest_single_disk_layout(
elif separate_home:
using_home_partition = True
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.set_focus_by_value(MenuItem.yes().value)
result = SelectMenu(
@ -390,7 +390,7 @@ def suggest_single_disk_layout(
type=PartitionType.Primary,
start=root_start,
length=root_length,
mountpoint=Path("/") if not using_subvolumes else None,
mountpoint=Path('/') if not using_subvolumes else None,
fs_type=filesystem_type,
mount_options=mount_options,
)
@ -402,10 +402,10 @@ def suggest_single_disk_layout(
# https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash
# https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh
subvolumes = [
SubvolumeModification(Path("@"), Path("/")),
SubvolumeModification(Path("@home"), Path("/home")),
SubvolumeModification(Path("@log"), Path("/var/log")),
SubvolumeModification(Path("@pkg"), Path("/var/cache/pacman/pkg")),
SubvolumeModification(Path('@'), Path('/')),
SubvolumeModification(Path('@home'), Path('/home')),
SubvolumeModification(Path('@log'), Path('/var/log')),
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
]
root_partition.btrfs_subvols = subvolumes
elif using_home_partition:
@ -424,7 +424,7 @@ def suggest_single_disk_layout(
type=PartitionType.Primary,
start=home_start,
length=home_length,
mountpoint=Path("/home"),
mountpoint=Path('/home'),
fs_type=filesystem_type,
mount_options=mount_options,
flags=flags,
@ -467,11 +467,11 @@ def suggest_multi_disk_layout(
root_device: BDevice | None = sorted_delta[0][0]
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("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('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 Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(Unit.GiB))
items = [MenuItem(tr("Continue"))]
items = [MenuItem(tr('Continue'))]
group = MenuItemGroup(items)
SelectMenu(group).run()
@ -480,11 +480,11 @@ def suggest_multi_disk_layout(
if filesystem_type == FilesystemType.Btrfs:
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"/root: {root_device.device_info.path}")
debug(f"/home: {home_device.device_info.path}")
debug(f'Suggesting multi-disk-layout for devices: {device_paths}')
debug(f'/root: {root_device.device_info.path}')
debug(f'/home: {home_device.device_info.path}')
root_device_modification = DeviceModification(root_device, wipe=True)
home_device_modification = DeviceModification(home_device, wipe=True)
@ -512,7 +512,7 @@ def suggest_multi_disk_layout(
type=PartitionType.Primary,
start=root_start,
length=root_length,
mountpoint=Path("/"),
mountpoint=Path('/'),
mount_options=mount_options,
fs_type=filesystem_type,
)
@ -534,7 +534,7 @@ def suggest_multi_disk_layout(
type=PartitionType.Primary,
start=home_start,
length=home_length,
mountpoint=Path("/home"),
mountpoint=Path('/home'),
mount_options=mount_options,
fs_type=filesystem_type,
flags=flags,
@ -547,10 +547,10 @@ def suggest_multi_disk_layout(
def suggest_lvm_layout(
disk_config: DiskLayoutConfiguration,
filesystem_type: FilesystemType | None = None,
vg_grp_name: str = "ArchinstallVg",
vg_grp_name: str = 'ArchinstallVg',
) -> LvmConfiguration:
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
btrfs_subvols = []
@ -561,7 +561,7 @@ def suggest_lvm_layout(
filesystem_type = select_main_filesystem_format()
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.set_focus_by_value(MenuItem.yes().value)
@ -580,10 +580,10 @@ def suggest_lvm_layout(
if using_subvolumes:
btrfs_subvols = [
SubvolumeModification(Path("@"), Path("/")),
SubvolumeModification(Path("@home"), Path("/home")),
SubvolumeModification(Path("@log"), Path("/var/log")),
SubvolumeModification(Path("@pkg"), Path("/var/cache/pacman/pkg")),
SubvolumeModification(Path('@'), Path('/')),
SubvolumeModification(Path('@home'), Path('/home')),
SubvolumeModification(Path('@log'), Path('/var/log')),
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
]
home_volume = False
@ -599,7 +599,7 @@ def suggest_lvm_layout(
other_part.append(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(
[p.length for p in other_part],
@ -612,10 +612,10 @@ def suggest_lvm_layout(
root_vol = LvmVolume(
status=LvmVolumeStatus.Create,
name="root",
name='root',
fs_type=filesystem_type,
length=root_vol_size,
mountpoint=Path("/"),
mountpoint=Path('/'),
btrfs_subvols=btrfs_subvols,
mount_options=mount_options,
)
@ -625,10 +625,10 @@ def suggest_lvm_layout(
if home_volume:
home_vol = LvmVolume(
status=LvmVolumeStatus.Create,
name="home",
name='home',
fs_type=filesystem_type,
length=home_vol_size,
mountpoint=Path("/home"),
mountpoint=Path('/home'),
)
lvm_vol_group.volumes.append(home_vol)

View File

@ -20,18 +20,18 @@ from ..translationhandler import Language
class PostInstallationAction(Enum):
EXIT = tr("Exit archinstall")
REBOOT = tr("Reboot system")
CHROOT = tr("chroot into installation for post-installation configurations")
EXIT = tr('Exit archinstall')
REBOOT = tr('Reboot system')
CHROOT = tr('chroot into installation for post-installation configurations')
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 += (
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()
@ -53,12 +53,12 @@ def ask_ntp(preset: bool = True) -> bool:
case ResultType.Selection:
return result.item() == MenuItem.yes()
case _:
raise ValueError("Unhandled return type")
raise ValueError('Unhandled return type')
def ask_hostname(preset: str | None = None) -> str | None:
result = EditMenu(
tr("Hostname"),
tr('Hostname'),
alignment=Alignment.CENTER,
allow_skip=True,
default_text=preset,
@ -73,11 +73,11 @@ def ask_hostname(preset: str | None = None) -> str | None:
return None
return hostname
case ResultType.Reset:
raise ValueError("Unhandled result type")
raise ValueError('Unhandled result type')
def ask_for_a_timezone(preset: str | None = None) -> str | None:
default = "UTC"
default = 'UTC'
timezones = list_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,
allow_reset=True,
allow_skip=True,
frame=FrameProperties.min(tr("Timezone")),
frame=FrameProperties.min(tr('Timezone')),
alignment=Alignment.CENTER,
).run()
@ -113,7 +113,7 @@ def ask_for_audio_selection(preset: AudioConfiguration | None = None) -> AudioCo
group,
allow_skip=True,
alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Audio")),
frame=FrameProperties.min(tr('Audio')),
).run()
match result.type_:
@ -122,7 +122,7 @@ def ask_for_audio_selection(preset: AudioConfiguration | None = None) -> AudioCo
case ResultType.Selection:
return AudioConfiguration(audio=result.get_value())
case ResultType.Reset:
raise ValueError("Unhandled result type")
raise ValueError('Unhandled result type')
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.")
# 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)
@ -147,9 +147,9 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
group = MenuItemGroup(items, sort_items=True)
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 += "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](
group,
@ -157,7 +157,7 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
allow_skip=True,
allow_reset=False,
alignment=Alignment.CENTER,
frame=FrameProperties.min(header=tr("Select language")),
frame=FrameProperties.min(header=tr('Select language')),
).run()
match result.type_:
@ -166,7 +166,7 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
case ResultType.Selection:
return result.get_value()
case ResultType.Reset:
raise ValueError("Language selection not handled")
raise ValueError('Language selection not handled')
def ask_additional_packages_to_install(
@ -175,18 +175,18 @@ def ask_additional_packages_to_install(
) -> list[str]:
repositories |= {Repository.Core, Repository.Extra}
respos_text = ", ".join([r.value for r in repositories])
output = tr("Repositories: {}").format(respos_text) + "\n"
respos_text = ', '.join([r.value for r in repositories])
output = tr('Repositories: {}').format(respos_text) + '\n'
output += tr("Loading packages...")
output += tr('Loading packages...')
Tui.print(output, clear_screen=True)
packages = list_available_packages(tuple(repositories))
package_groups = PackageGroup.from_available_packages(packages)
# 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("Select any packages from the below list that should be installed additionally") + "\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'
# there are over 15k packages so this needs to be quick
preset_packages: list[AvailablePackage | PackageGroup] = []
@ -224,9 +224,9 @@ def ask_additional_packages_to_install(
allow_reset=True,
allow_skip=True,
multi=True,
preview_frame=FrameProperties.max("Package info"),
preview_frame=FrameProperties.max('Package info'),
preview_style=PreviewStyle.RIGHT,
preview_size="auto",
preview_size='auto',
).run()
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:
max_recommended = 5
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(" - 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('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(' - 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')
def validator(s: str) -> str | None:
try:
@ -255,10 +255,10 @@ def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
except Exception:
pass
return tr("Invalid download number")
return tr('Invalid download number')
result = EditMenu(
tr("Number downloads"),
tr('Number downloads'),
header=header,
allow_skip=True,
allow_reset=True,
@ -276,23 +276,23 @@ def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
case _:
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:
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:
if "ParallelDownloads" in line:
fwrite.write(f"ParallelDownloads = {downloads}\n")
if 'ParallelDownloads' in line:
fwrite.write(f'ParallelDownloads = {downloads}\n')
else:
fwrite.write(f"{line}\n")
fwrite.write(f'{line}\n')
return downloads
def ask_post_installation() -> PostInstallationAction:
header = tr("Installation completed") + "\n\n"
header += tr("What would you like to do next?") + "\n"
header = tr('Installation completed') + '\n\n'
header += tr('What would you like to do next?') + '\n'
items = [MenuItem(action.value, value=action) for action in PostInstallationAction]
group = MenuItemGroup(items)
@ -308,11 +308,11 @@ def ask_post_installation() -> PostInstallationAction:
case ResultType.Selection:
return result.get_value()
case _:
raise ValueError("Post installation action not handled")
raise ValueError('Post installation action not handled')
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()
result = SelectMenu[bool](

View File

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

View File

@ -17,9 +17,9 @@ from ..networking import list_interfaces
class ManualNetworkConfig(ListManager[Nic]):
def __init__(self, prompt: str, preset: list[Nic]):
self._actions = [
tr("Add interface"),
tr("Edit interface"),
tr("Delete interface"),
tr('Add interface'),
tr('Edit interface'),
tr('Delete interface'),
]
super().__init__(
@ -31,7 +31,7 @@ class ManualNetworkConfig(ListManager[Nic]):
@override
def selected_action_display(self, selection: Nic) -> str:
return selection.iface if selection.iface else ""
return selection.iface if selection.iface else ''
@override
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](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Interfaces")),
frame=FrameProperties.min(tr('Interfaces')),
allow_skip=True,
).run()
@ -77,7 +77,7 @@ class ManualNetworkConfig(ListManager[Nic]):
case ResultType.Selection:
return result.get_value()
case ResultType.Reset:
raise ValueError("Unhandled result type")
raise ValueError('Unhandled result type')
def _get_ip_address(
self,
@ -89,7 +89,7 @@ class ManualNetworkConfig(ListManager[Nic]):
) -> str | None:
def validator(ip: str) -> str | None:
if multi:
ips = ip.split(" ")
ips = ip.split(' ')
else:
ips = [ip]
@ -98,7 +98,7 @@ class ManualNetworkConfig(ListManager[Nic]):
ipaddress.ip_interface(ip)
return None
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(
title,
@ -114,14 +114,14 @@ class ManualNetworkConfig(ListManager[Nic]):
case ResultType.Selection:
return result.text()
case ResultType.Reset:
raise ValueError("Unhandled result type")
raise ValueError('Unhandled result type')
def _edit_iface(self, edit_nic: Nic) -> Nic:
iface_name = edit_nic.iface
modes = ["DHCP (auto detect)", "IP (static)"]
default_mode = "DHCP (auto detect)"
modes = ['DHCP (auto detect)', 'IP (static)']
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]
group = MenuItemGroup(items, sort_items=True)
group.set_default_by_value(default_mode)
@ -131,34 +131,34 @@ class ManualNetworkConfig(ListManager[Nic]):
header=header,
allow_skip=False,
alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Modes")),
frame=FrameProperties.min(tr('Modes')),
).run()
match result.type_:
case ResultType.Selection:
mode = result.get_value()
case ResultType.Reset:
raise ValueError("Unhandled result type")
raise ValueError('Unhandled result type')
case ResultType.Skip:
raise ValueError("The mode menu should not be skippable")
raise ValueError('The mode menu should not be skippable')
case _:
assert_never(result.type_)
if mode == "IP (static)":
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)
if mode == 'IP (static)':
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)
header = tr("Enter your gateway (router) IP address (leave blank for none)") + "\n"
gateway = self._get_ip_address(tr("Gateway address"), header, True, False)
header = tr('Enter your gateway (router) IP address (leave blank for none)') + '\n'
gateway = self._get_ip_address(tr('Gateway address'), header, True, False)
if edit_nic.dns:
display_dns = " ".join(edit_nic.dns)
display_dns = ' '.join(edit_nic.dns)
else:
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(
tr("DNS servers"),
tr('DNS servers'),
header,
True,
True,
@ -167,7 +167,7 @@ class ManualNetworkConfig(ListManager[Nic]):
dns = []
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)
else:
@ -189,7 +189,7 @@ def ask_to_configure_network(preset: NetworkConfiguration | None) -> NetworkConf
result = SelectMenu[NetworkConfiguration](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Network configuration")),
frame=FrameProperties.min(tr('Network configuration')),
allow_reset=True,
allow_skip=True,
).run()
@ -209,7 +209,7 @@ def ask_to_configure_network(preset: NetworkConfiguration | None) -> NetworkConf
return NetworkConfiguration(NicType.NM)
case NicType.MANUAL:
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:
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
:rtype: string
"""
kernels = ["linux", "linux-lts", "linux-zen", "linux-hardened"]
default_kernel = "linux"
kernels = ['linux', 'linux-lts', 'linux-zen', 'linux-hardened']
default_kernel = 'linux'
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_reset=True,
alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Kernel")),
frame=FrameProperties.min(tr('Kernel')),
multi=True,
).run()
@ -50,7 +50,7 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
if not SysInfo.has_uefi():
options = [Bootloader.Grub, Bootloader.Limine]
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:
options = [b for b in Bootloader]
default = Bootloader.Systemd
@ -65,7 +65,7 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
group,
header=header,
alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Bootloader")),
frame=FrameProperties.min(tr('Bootloader')),
allow_skip=True,
).run()
@ -75,11 +75,11 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
case ResultType.Selection:
return result.get_value()
case ResultType.Reset:
raise ValueError("Unhandled result type")
raise ValueError('Unhandled result type')
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.set_focus_by_value(preset)
@ -99,7 +99,7 @@ def ask_for_uki(preset: bool = True) -> bool:
case ResultType.Selection:
return result.item() == MenuItem.yes()
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:
@ -120,22 +120,22 @@ def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None
if preset is not None:
group.set_focus_by_value(preset)
header = ""
header = ''
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():
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():
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](
group,
header=header,
allow_skip=True,
allow_reset=True,
preview_size="auto",
preview_size='auto',
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()
match result.type_:
@ -153,7 +153,7 @@ def ask_for_swap(preset: bool = True) -> bool:
else:
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.set_focus_by_value(default_item)
@ -173,6 +173,6 @@ def ask_for_swap(preset: bool = True) -> bool:
case ResultType.Selection:
return result.item() == MenuItem.yes()
case ResultType.Reset:
raise ValueError("Unhandled result type")
raise ValueError('Unhandled result type')
return preset

View File

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

View File

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

View File

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

View File

@ -24,25 +24,25 @@ class Luks2:
@property
def mapper_dev(self) -> Path | None:
if self.mapper_name:
return Path(f"/dev/mapper/{self.mapper_name}")
return Path(f'/dev/mapper/{self.mapper_name}')
return None
def isLuks(self) -> bool:
try:
SysCommand(f"cryptsetup isLuks {self.luks_dev_path}")
SysCommand(f'cryptsetup isLuks {self.luks_dev_path}')
return True
except SysCallError:
return False
def erase(self) -> None:
debug(f"Erasing luks partition: {self.luks_dev_path}")
worker = SysCommandWorker(f"cryptsetup erase {self.luks_dev_path}")
debug(f'Erasing luks partition: {self.luks_dev_path}')
worker = SysCommandWorker(f'cryptsetup erase {self.luks_dev_path}')
worker.poll()
worker.write(b"YES\n", line_ending=False)
worker.write(b'YES\n', line_ending=False)
def __post_init__(self) -> 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:
self.unlock(self.key_file)
@ -53,12 +53,12 @@ class Luks2:
def _password_bytes(self) -> bytes:
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):
return self.password
else:
return bytes(self.password.plaintext, "UTF-8")
return bytes(self.password.plaintext, 'UTF-8')
def _get_passphrase_args(
self,
@ -67,42 +67,42 @@ class Luks2:
key_file = key_file or self.key_file
if key_file:
return ["--key-file", str(key_file)], None
return ['--key-file', str(key_file)], None
return [], self._password_bytes()
def encrypt(
self,
key_size: int = 512,
hash_type: str = "sha512",
hash_type: str = 'sha512',
iter_time: int = 10000,
key_file: Path | None = 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)
cmd = [
"cryptsetup",
"--batch-mode",
"--verbose",
"--type",
"luks2",
"--pbkdf",
"argon2id",
"--hash",
'cryptsetup',
'--batch-mode',
'--verbose',
'--type',
'luks2',
'--pbkdf',
'argon2id',
'--hash',
hash_type,
"--key-size",
'--key-size',
str(key_size),
"--iter-time",
'--iter-time',
str(iter_time),
*key_file_arg,
"--use-urandom",
"luksFormat",
'--use-urandom',
'luksFormat',
str(self.luks_dev_path),
]
debug(f"cryptsetup format: {shlex.join(cmd)}")
debug(f'cryptsetup format: {shlex.join(cmd)}')
try:
result = run(cmd, input_data=passphrase)
@ -110,23 +110,23 @@ class Luks2:
output = err.stdout.decode().rstrip()
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
return key_file
def _get_luks_uuid(self) -> str:
command = f"cryptsetup luksUUID {self.luks_dev_path}"
command = f'cryptsetup luksUUID {self.luks_dev_path}'
try:
return SysCommand(command).decode()
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
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:
"""
@ -136,29 +136,29 @@ class Luks2:
:param key_file: An alternative key file
: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:
raise ValueError("mapper name missing")
raise ValueError('mapper name missing')
key_file_arg, passphrase = self._get_passphrase_args(key_file)
cmd = [
"cryptsetup",
"open",
'cryptsetup',
'open',
str(self.luks_dev_path),
str(self.mapper_name),
*key_file_arg,
"--type",
"luks2",
'--type',
'luks2',
]
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():
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:
umount(self.luks_dev_path)
@ -171,32 +171,32 @@ class Luks2:
for child in lsblk_info.children:
# Unmount the child location
for mountpoint in child.mountpoints:
debug(f"Unmounting {mountpoint}")
debug(f'Unmounting {mountpoint}')
umount(mountpoint, recursive=True)
# And close it if possible.
debug(f"Closing crypt device {child.name}")
SysCommand(f"cryptsetup close {child.name}")
debug(f'Closing crypt device {child.name}')
SysCommand(f'cryptsetup close {child.name}')
def create_keyfile(self, target_path: Path, override: bool = False) -> None:
"""
Routine to create keyfiles, so it can be moved elsewhere
"""
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
# 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)
crypttab_path = target_path / "etc/crypttab"
crypttab_path = target_path / 'etc/crypttab'
if key_file.exists():
if not override:
info(f"Key file {key_file} already exists, keeping existing")
info(f'Key file {key_file} already exists, keeping existing')
return
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)
@ -206,22 +206,22 @@ class Luks2:
key_file.chmod(0o400)
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:
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)
pw_injected = False
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())
pw_injected = True
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(
self,
@ -229,10 +229,10 @@ class Luks2:
key_file: Path,
options: list[str],
) -> 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:
opt = ",".join(options)
with open(crypttab_path, 'a') as crypttab:
opt = ','.join(options)
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)

View File

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

View File

@ -10,7 +10,7 @@ from archinstall.tui.types import Chars, FrameProperties, FrameStyle, PreviewSty
from ..output import error
CONFIG_KEY = "__config__"
CONFIG_KEY = '__config__'
class AbstractMenu[ValueT]:
@ -41,7 +41,7 @@ class AbstractMenu[ValueT]:
# TODO: skip processing when it comes from a planified exit
if len(args) >= 2 and 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]
self.sync_all_to_config()
@ -82,7 +82,7 @@ class AbstractMenu[ValueT]:
found = True
if not found:
raise ValueError(f"No selector found: {key}")
raise ValueError(f'No selector found: {key}')
def disable_all(self) -> None:
for item in self._menu_item_group.items:
@ -101,8 +101,8 @@ class AbstractMenu[ValueT]:
allow_reset=self._allow_reset,
reset_warning_msg=self._reset_warning,
preview_style=PreviewStyle.RIGHT,
preview_size="auto",
preview_frame=FrameProperties("Info", FrameStyle.MAX),
preview_size='auto',
preview_frame=FrameProperties('Info', FrameStyle.MAX),
).run()
match result.type_:
@ -128,7 +128,7 @@ class AbstractSubMenu[ValueT](AbstractMenu[ValueT]):
auto_cursor: bool = True,
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))
super().__init__(

View File

@ -36,9 +36,9 @@ class ListManager[ValueT]:
self._prompt = prompt
self._separator = ""
self._confirm_action = tr("Confirm and exit")
self._cancel_action = tr("Cancel")
self._separator = ''
self._confirm_action = tr('Confirm and exit')
self._cancel_action = tr('Cancel')
self._terminate_actions = [self._confirm_action, self._cancel_action]
self._base_actions = base_actions
@ -66,7 +66,7 @@ class ListManager[ValueT]:
prompt = None
if self._prompt is not None:
prompt = f"{self._prompt}\n\n"
prompt = f'{self._prompt}\n\n'
prompt = None
@ -82,7 +82,7 @@ class ListManager[ValueT]:
case ResultType.Selection:
value = result.get_value()
case _:
raise ValueError("Unhandled return type")
raise ValueError('Unhandled return type')
if value in self._base_actions:
value = cast(str, value)
@ -108,7 +108,7 @@ class ListManager[ValueT]:
items = [MenuItem(o, value=o) for o in options]
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](
group,
@ -122,7 +122,7 @@ class ListManager[ValueT]:
case ResultType.Selection:
value = result.get_value()
case _:
raise ValueError("Unhandled return type")
raise ValueError('Unhandled return type')
if value != self._cancel_action:
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
"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]:
"""
this function is called when a base action or
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]:
"""

View File

@ -10,7 +10,7 @@ class MenuHelper:
data: list[Any],
additional_options: list[str] = [],
) -> None:
self._separator = ""
self._separator = ''
self._data = data
self._additional_options = additional_options
@ -39,10 +39,10 @@ class MenuHelper:
if data:
table = FormattedOutput.as_table(data)
rows = table.split("\n")
rows = table.split('\n')
# 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):
display_data[row] = entry

View File

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

View File

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

View File

@ -10,7 +10,7 @@ if TYPE_CHECKING:
class Audio(StrEnum):
NO_AUDIO = "No audio server"
NO_AUDIO = 'No audio server'
PIPEWIRE = auto()
PULSEAUDIO = auto()
@ -21,20 +21,20 @@ class AudioConfiguration:
def json(self) -> dict[str, str]:
return {
"audio": self.audio.value,
'audio': self.audio.value,
}
@staticmethod
def parse_arg(arg: dict[str, str]) -> "AudioConfiguration":
def parse_arg(arg: dict[str, str]) -> 'AudioConfiguration':
return AudioConfiguration(
Audio(arg["audio"]),
Audio(arg['audio']),
)
def install_audio_config(
self,
installation: "Installer",
installation: 'Installer',
) -> None:
info(f"Installing audio server: {self.audio.name}")
info(f'Installing audio server: {self.audio.name}')
from ...default_profiles.applications.pipewire import PipewireProfile
@ -42,11 +42,11 @@ class AudioConfiguration:
case Audio.PIPEWIRE:
PipewireProfile().install(installation)
case Audio.PULSEAUDIO:
installation.add_additional_packages("pulseaudio")
installation.add_additional_packages('pulseaudio')
if self.audio != Audio.NO_AUDIO:
if SysInfo.requires_sof_fw():
installation.add_additional_packages("sof-firmware")
installation.add_additional_packages('sof-firmware')
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):
Systemd = "Systemd-boot"
Grub = "Grub"
Efistub = "Efistub"
Limine = "Limine"
Systemd = 'Systemd-boot'
Grub = 'Grub'
Efistub = 'Efistub'
Limine = 'Limine'
def has_uki_support(self) -> bool:
match self:
@ -40,7 +40,7 @@ class Bootloader(Enum):
bootloader = bootloader.capitalize()
if bootloader not in cls.values():
values = ", ".join(cls.values())
values = ', '.join(cls.values())
warn(f'Invalid bootloader value "{bootloader}". Allowed values: {values}')
sys.exit(1)
return Bootloader(bootloader)

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -38,7 +38,7 @@ class MirrorStatusEntryV3(BaseModel):
@property
def server_url(self) -> str:
return f"{self.url}$repo/os/$arch"
return f'{self.url}$repo/os/$arch'
@property
def speed(self) -> float:
@ -50,8 +50,8 @@ class MirrorStatusEntryV3(BaseModel):
retry = 0
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")
req = urllib.request.Request(url=f"{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')
try:
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
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
except urllib.error.URLError as error:
debug(f" speed: <undetermined> ({error}), skip")
debug(f' speed: <undetermined> ({error}), skip')
self._speed = 0
# Do retry error
except (http.client.IncompleteRead, ConnectionResetError) as error:
debug(f" speed: <undetermined> ({error}), retry")
debug(f' speed: <undetermined> ({error}), retry')
# Catch all
except Exception as error:
debug(f" speed: <undetermined> ({error}), skip")
debug(f' speed: <undetermined> ({error}), skip')
self._speed = 0
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.
"""
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)
debug(f" latency: {self._latency}")
debug(f' latency: {self._latency}')
return self._latency
@classmethod
@field_validator("score", mode="before")
@field_validator('score', mode='before')
def validate_score(cls, value: float) -> int | None:
if value is not None:
value = round(value)
debug(f" score: {value}")
debug(f' score: {value}')
return value
@model_validator(mode="after")
def debug_output(self, validation_info) -> "MirrorStatusEntryV3":
self._hostname, *port = urllib.parse.urlparse(self.url).netloc.split(":", 1)
@model_validator(mode='after')
def debug_output(self, validation_info) -> 'MirrorStatusEntryV3':
self._hostname, *port = urllib.parse.urlparse(self.url).netloc.split(':', 1)
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
@ -118,16 +118,16 @@ class MirrorStatusListV3(BaseModel):
urls: list[MirrorStatusEntryV3]
version: int
@model_validator(mode="before")
@model_validator(mode='before')
@classmethod
def check_model(
cls,
data: 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
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
@ -146,14 +146,14 @@ class MirrorRegion:
class SignCheck(Enum):
Never = "Never"
Optional = "Optional"
Required = "Required"
Never = 'Never'
Optional = 'Optional'
Required = 'Required'
class SignOption(Enum):
TrustedOnly = "TrustedOnly"
TrustAll = "TrustAll"
TrustedOnly = 'TrustedOnly'
TrustAll = 'TrustAll'
class _CustomRepositorySerialization(TypedDict):
@ -172,30 +172,30 @@ class CustomRepository:
def table_data(self) -> dict[str, str]:
return {
"Name": self.name,
"Url": self.url,
"Sign check": self.sign_check.value,
"Sign options": self.sign_option.value,
'Name': self.name,
'Url': self.url,
'Sign check': self.sign_check.value,
'Sign options': self.sign_option.value,
}
def json(self) -> _CustomRepositorySerialization:
return {
"name": self.name,
"url": self.url,
"sign_check": self.sign_check.value,
"sign_option": self.sign_option.value,
'name': self.name,
'url': self.url,
'sign_check': self.sign_check.value,
'sign_option': self.sign_option.value,
}
@classmethod
def parse_args(cls, args: list[dict[str, str]]) -> list["CustomRepository"]:
def parse_args(cls, args: list[dict[str, str]]) -> list['CustomRepository']:
configs = []
for arg in args:
configs.append(
CustomRepository(
arg["name"],
arg["url"],
SignCheck(arg["sign_check"]),
SignOption(arg["sign_option"]),
arg['name'],
arg['url'],
SignCheck(arg['sign_check']),
SignOption(arg['sign_option']),
),
)
@ -207,17 +207,17 @@ class CustomServer:
url: str
def table_data(self) -> dict[str, str]:
return {"Url": self.url}
return {'Url': self.url}
def json(self) -> dict[str, str]:
return {"url": self.url}
return {'url': self.url}
@classmethod
def parse_args(cls, args: list[dict[str, str]]) -> list["CustomServer"]:
def parse_args(cls, args: list[dict[str, str]]) -> list['CustomServer']:
configs = []
for arg in args:
configs.append(
CustomServer(arg["url"]),
CustomServer(arg['url']),
)
return configs
@ -239,11 +239,11 @@ class MirrorConfiguration:
@property
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
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:
regions = {}
@ -251,26 +251,26 @@ class MirrorConfiguration:
regions.update(m.json())
return {
"mirror_regions": regions,
"custom_servers": self.custom_servers,
"optional_repositories": [r.value for r in self.optional_repositories],
"custom_repositories": [c.json() for c in self.custom_repositories],
'mirror_regions': regions,
'custom_servers': self.custom_servers,
'optional_repositories': [r.value for r in self.optional_repositories],
'custom_repositories': [c.json() for c in self.custom_repositories],
}
def custom_servers_config(self) -> str:
config = ""
config = ''
if self.custom_servers:
config += "## Custom Servers\n"
config += '## Custom Servers\n'
for server in self.custom_servers:
config += f"Server = {server.url}\n"
config += f'Server = {server.url}\n'
return config.strip()
def regions_config(self, speed_sort: bool = True) -> str:
from ..mirrors import mirror_list_handler
config = ""
config = ''
for mirror_region in self.mirror_regions:
sorted_stati = mirror_list_handler.get_status_by_region(
@ -278,20 +278,20 @@ class MirrorConfiguration:
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:
config += f"Server = {status.server_url}\n"
config += f'Server = {status.server_url}\n'
return config
def repositories_config(self) -> str:
config = ""
config = ''
for repo in self.custom_repositories:
config += f"\n\n[{repo.name}]\n"
config += f"SigLevel = {repo.sign_check.value} {repo.sign_option.value}\n"
config += f"Server = {repo.url}\n"
config += f'\n\n[{repo.name}]\n'
config += f'SigLevel = {repo.sign_check.value} {repo.sign_option.value}\n'
config += f'Server = {repo.url}\n'
return config
@ -300,25 +300,25 @@ class MirrorConfiguration:
cls,
args: dict[str, Any],
backwards_compatible_repo: list[Repository] = [],
) -> "MirrorConfiguration":
) -> 'MirrorConfiguration':
config = MirrorConfiguration()
mirror_regions = args.get("mirror_regions", [])
mirror_regions = args.get('mirror_regions', [])
if mirror_regions:
for region, urls in mirror_regions.items():
config.mirror_regions.append(MirrorRegion(region, urls))
if args.get("custom_servers"):
config.custom_servers = CustomServer.parse_args(args["custom_servers"])
if args.get('custom_servers'):
config.custom_servers = CustomServer.parse_args(args['custom_servers'])
# backwards compatibility with the new custom_repository
if "custom_mirrors" in args:
config.custom_repositories = CustomRepository.parse_args(args["custom_mirrors"])
if "custom_repositories" in args:
config.custom_repositories = CustomRepository.parse_args(args["custom_repositories"])
if 'custom_mirrors' in args:
config.custom_repositories = CustomRepository.parse_args(args['custom_mirrors'])
if 'custom_repositories' in args:
config.custom_repositories = CustomRepository.parse_args(args['custom_repositories'])
if "optional_repositories" in args:
config.optional_repositories = [Repository(r) for r in args["optional_repositories"]]
if 'optional_repositories' in args:
config.optional_repositories = [Repository(r) for r in args['optional_repositories']]
if backwards_compatible_repo:
for r in backwards_compatible_repo:

View File

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

View File

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

View File

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

View File

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

View File

@ -39,7 +39,7 @@ class DownloadTimer:
"""
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:
if self.timeout > 0:
@ -72,27 +72,27 @@ def get_hw_addr(ifname: str) -> str:
import fcntl
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
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])
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])
def list_interfaces(skip_loopback: bool = True) -> dict[str, str]:
interfaces = {}
for _index, iface in socket.if_nameindex():
if skip_loopback and iface == "lo":
if skip_loopback and iface == 'lo':
continue
mac = get_hw_addr(iface).replace(":", "-").lower()
mac = get_hw_addr(iface).replace(':', '-').lower()
interfaces[mac] = iface
return interfaces
def update_keyring() -> bool:
info("Updating archlinux-keyring ...")
info('Updating archlinux-keyring ...')
try:
Pacman.run("-Sy --noconfirm archlinux-keyring")
Pacman.run('-Sy --noconfirm archlinux-keyring')
return True
except SysCallError:
if os.geteuid() != 0:
@ -105,18 +105,18 @@ def enrich_iface_types(interfaces: list[str]) -> dict[str, str]:
result = {}
for iface in interfaces:
if os.path.isdir(f"/sys/class/net/{iface}/bridge/"):
result[iface] = "BRIDGE"
elif os.path.isfile(f"/sys/class/net/{iface}/tun_flags"):
if os.path.isdir(f'/sys/class/net/{iface}/bridge/'):
result[iface] = 'BRIDGE'
elif os.path.isfile(f'/sys/class/net/{iface}/tun_flags'):
# ethtool -i {iface}
result[iface] = "TUN/TAP"
elif os.path.isdir(f"/sys/class/net/{iface}/device"):
if os.path.isdir(f"/sys/class/net/{iface}/wireless/"):
result[iface] = "WIRELESS"
result[iface] = 'TUN/TAP'
elif os.path.isdir(f'/sys/class/net/{iface}/device'):
if os.path.isdir(f'/sys/class/net/{iface}/wireless/'):
result[iface] = 'WIRELESS'
else:
result[iface] = "PHYSICAL"
result[iface] = 'PHYSICAL'
else:
result[iface] = "UNKNOWN"
result[iface] = 'UNKNOWN'
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:
encoded = urlencode(params)
full_url = f"{url}?{encoded}"
full_url = f'{url}?{encoded}'
else:
full_url = url
try:
response = urlopen(full_url, context=ssl_context)
data = response.read().decode("UTF-8")
data = response.read().decode('UTF-8')
return data
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:
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:
# Calculate the ICMP checksum
checksum = 0
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 & 0xFFFF
@ -156,17 +156,17 @@ def calc_checksum(icmp_packet: bytes) -> int:
def build_icmp(payload: bytes) -> bytes:
# 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)
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:
watchdog = select.epoll()
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)
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:
for _fileno, _event in watchdog.poll(0.1):
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)
if icmp_type == 0 and response[-len(random_identifier) :] == random_identifier:
latency = round((time.time() - started) * 1000)
break
except OSError as e:
debug(f"Error: {e}")
debug(f'Error: {e}')
break
icmp_socket.close()

View File

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

View File

@ -11,9 +11,9 @@ from ..models.packages import AvailablePackage, LocalPackage, PackageSearch, Pac
from ..output import debug
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_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:
@ -22,7 +22,7 @@ def _make_request(url: str, params: dict[str, str]) -> addinfourl:
ssl_context.verify_mode = ssl.CERT_NONE
encoded = urlencode(params)
full_url = f"{url}?{encoded}"
full_url = f'{url}?{encoded}'
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]:
# TODO UPSTREAM: Implement /json/ for the groups search
try:
response = _make_request(BASE_GROUP_URL, {"name": name})
response = _make_request(BASE_GROUP_URL, {'name': name})
except HTTPError as err:
if err.code == 404:
return []
@ -38,9 +38,9 @@ def group_search(name: str) -> list[PackageSearchResult]:
raise err
# 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:
@ -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: 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:
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)
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:
package_info = []
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)
except SysCallError:
pass
@ -127,15 +127,15 @@ def list_available_packages(
filtered_repos = [name for repo in repositories for name in repo.get_repository_list()]
try:
Pacman.run("-Sy")
Pacman.run('-Sy')
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()
current_package.append(dec_line)
if dec_line.startswith("Validated"):
if dec_line.startswith('Validated'):
if current_package:
avail_pkg = _parse_package_output(current_package, AvailablePackage)
if avail_pkg.repository in filtered_repos:
@ -147,7 +147,7 @@ def list_available_packages(
@lru_cache(maxsize=128)
def _normalize_key_name(key: str) -> str:
return key.strip().lower().replace(" ", "_")
return key.strip().lower().replace(' ', '_')
def _parse_package_output[PackageType: (AvailablePackage, LocalPackage)](
@ -157,8 +157,8 @@ def _parse_package_output[PackageType: (AvailablePackage, LocalPackage)](
package = {}
for line in package_meta:
if ":" in line:
key, value = line.split(":", 1)
if ':' in line:
key, value = line.split(':', 1)
key = _normalize_key_name(key)
package[key] = value.strip()

View File

@ -18,26 +18,26 @@ class Pacman:
self.target = target
@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.
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.
"""
pacman_db_lock = Path("/var/lib/pacman/db.lck")
pacman_db_lock = Path('/var/lib/pacman/db.lck')
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()
while pacman_db_lock.exists():
time.sleep(0.25)
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)
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]
while True:
@ -45,20 +45,20 @@ class Pacman:
func(*args, **kwargs)
break
except Exception as 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":
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':
continue
raise RequirementError(f"{bail_message}: {err}")
raise RequirementError(f'{bail_message}: {err}')
def sync(self) -> None:
if self.synced:
return
self.ask(
"Could not sync a new package database",
"Could not sync mirrors",
'Could not sync a new package database',
'Could not sync mirrors',
self.run,
"-Syy",
default_cmd="pacman",
'-Syy',
default_cmd='pacman',
)
self.synced = True
@ -68,22 +68,22 @@ class Pacman:
packages = [packages]
for plugin in plugins.values():
if hasattr(plugin, "on_pacstrap"):
if hasattr(plugin, 'on_pacstrap'):
if result := plugin.on_pacstrap(packages):
packages = result
info(f"Installing packages: {packages}")
info(f'Installing packages: {packages}')
self.ask(
"Could not strap in packages",
"Pacstrap failed. See /var/log/archinstall/install.log or above message for error details",
'Could not strap in packages',
'Pacstrap failed. See /var/log/archinstall/install.log or above message for error details',
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,
)
__all__ = [
"Pacman",
"PacmanConfig",
'Pacman',
'PacmanConfig',
]

View File

@ -7,10 +7,10 @@ from ..models.packages import Repository
class PacmanConfig:
def __init__(self, target: Path | None):
self._config_path = Path("/etc") / "pacman.conf"
self._config_path = Path('/etc') / 'pacman.conf'
if target:
self._config_remote_path = target / "etc" / "pacman.conf"
self._config_remote_path = target / 'etc' / 'pacman.conf'
self._repositories: list[Repository] = []
@ -27,7 +27,7 @@ class PacmanConfig:
repos_to_enable = []
for repo in self._repositories:
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:
repos_to_enable.append(repo.value)
@ -35,18 +35,18 @@ class PacmanConfig:
for row, line in enumerate(content):
# 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:
# 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
if row + 1 < len(content) and content[row + 1].lstrip().startswith("#"):
content[row + 1] = re.sub(r"^#\s*", "", content[row + 1])
if row + 1 < len(content) and content[row + 1].lstrip().startswith('#'):
content[row + 1] = re.sub(r'^#\s*', '', content[row + 1])
# 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)
def persist(self) -> None:

View File

@ -16,15 +16,15 @@ plugins = {}
# 1: List archinstall.plugin definitions
# 2: Load the plugin entrypoint
# 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()
try:
plugins[plugin_definition.name] = plugin_entrypoint()
except Exception as err:
error(
f"Error: {err}",
f"The above error was detected when loading the plugin: {plugin_definition}",
f'Error: {err}',
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))
if url.scheme and url.scheme in ("https", "http"):
converted_path = Path(f"/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py")
if url.scheme and url.scheme in ('https', 'http'):
converted_path = Path(f'/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py')
with open(converted_path, "w") as temp_file:
temp_file.write(urllib.request.urlopen(url.geturl()).read().decode("utf-8"))
with open(converted_path, 'w') as temp_file:
temp_file.write(urllib.request.urlopen(url.geturl()).read().decode('utf-8'))
return converted_path
else:
@ -49,7 +49,7 @@ def _import_via_path(path: Path, namespace: str | None = None) -> str | None:
if not namespace:
namespace = os.path.basename(path)
if namespace == "__init__.py":
if namespace == '__init__.py':
namespace = path.parent.name
try:
@ -62,8 +62,8 @@ def _import_via_path(path: Path, namespace: str | None = None) -> str | None:
return namespace
except Exception as err:
error(
f"Error: {err}",
f"The above error was detected when loading the plugin: {path}",
f'Error: {err}',
f'The above error was detected when loading the plugin: {path}',
)
try:
@ -84,36 +84,36 @@ def _find_nth(haystack: list[str], needle: str, n: int) -> int | None:
def load_plugin(path: Path) -> None:
namespace: str | None = None
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
if not parsed_url.scheme:
# Path was not found in any known examples, check if it's an absolute path
if os.path.isfile(path):
namespace = _import_via_path(path)
elif parsed_url.scheme in ("https", "http"):
elif parsed_url.scheme in ('https', 'http'):
localized = _localize_path(path)
namespace = _import_via_path(localized)
if namespace and namespace in sys.modules:
# 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.
if hasattr(sys.modules[namespace], "__archinstall__version__"):
archinstall_major_and_minor_version = float(storage["__version__"][: _find_nth(storage["__version__"], ".", 2)])
if hasattr(sys.modules[namespace], '__archinstall__version__'):
archinstall_major_and_minor_version = float(storage['__version__'][: _find_nth(storage['__version__'], '.', 2)])
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()
# This in accordance with the entry_points() from setup.cfg above
if hasattr(sys.modules[namespace], "Plugin"):
if hasattr(sys.modules[namespace], 'Plugin'):
try:
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:
error(
f"Error: {err}",
f"The above error was detected when initiating the plugin: {path}",
f'Error: {err}',
f'The above error was detected when initiating the plugin: {path}',
)
else:
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]:
return [
MenuItem(
text=tr("Type"),
text=tr('Type'),
action=self._select_profile,
value=self._profile_config.profile,
preview_action=self._preview_profile,
key="profile",
key='profile',
),
MenuItem(
text=tr("Graphics driver"),
text=tr('Graphics 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,
preview_action=self._prev_gfx,
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
dependencies=["profile"],
key="gfx_driver",
dependencies=['profile'],
key='gfx_driver',
),
MenuItem(
text=tr("Greeter"),
text=tr('Greeter'),
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,
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
preview_action=self._prev_greeter,
dependencies=["profile"],
key="greeter",
dependencies=['profile'],
key='greeter',
),
]
@ -73,36 +73,36 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
if profile is not None:
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").value = None
self._item_group.find_by_key('gfx_driver').enabled = False
self._item_group.find_by_key('gfx_driver').value = None
else:
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').enabled = True
self._item_group.find_by_key('gfx_driver').value = GfxDriver.AllOpenSource
if not profile.is_greeter_supported():
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').enabled = False
self._item_group.find_by_key('greeter').value = None
else:
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').enabled = True
self._item_group.find_by_key('greeter').value = profile.default_greeter_type
else:
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('gfx_driver').value = None
self._item_group.find_by_key('greeter').value = None
return profile
def _select_gfx_driver(self, preset: GfxDriver | None = None) -> GfxDriver | None:
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.is_graphic_driver_supported():
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():
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('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'
group = MenuItemGroup.yes_no()
group.focus_item = MenuItem.no()
@ -126,25 +126,25 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
if item.value:
driver = item.get_value().value
packages = item.get_value().packages_text()
return f"Driver: {driver}\n{packages}"
return f'Driver: {driver}\n{packages}'
return None
def _prev_greeter(self, item: MenuItem) -> str | None:
if item.value:
return f"{tr('Greeter')}: {item.value.value}"
return f'{tr("Greeter")}: {item.value.value}'
return None
def _preview_profile(self, item: MenuItem) -> str | None:
profile: Profile | None = item.value
text = ""
text = ''
if profile:
if (sub_profiles := profile.current_selection) is not None:
text += tr("Selected profiles: ")
text += ", ".join([p.name for p in sub_profiles]) + "\n"
text += tr('Selected profiles: ')
text += ', '.join([p.name for p in sub_profiles]) + '\n'
if packages := profile.packages_text(include_sub_packages=True):
text += f"{packages}"
text += f'{packages}'
if text:
return text
@ -172,7 +172,7 @@ def select_greeter(
result = SelectMenu[GreeterType](
group,
allow_skip=True,
frame=FrameProperties.min(tr("Greeter")),
frame=FrameProperties.min(tr('Greeter')),
alignment=Alignment.CENTER,
).run()
@ -182,7 +182,7 @@ def select_greeter(
case ResultType.Selection:
return result.get_value()
case ResultType.Reset:
raise ValueError("Unhandled result type")
raise ValueError('Unhandled result type')
return None
@ -197,7 +197,7 @@ def select_profile(
top_level_profiles = profile_handler.get_top_level_profiles()
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]
group = MenuItemGroup(items, sort_items=True)
@ -209,7 +209,7 @@ def select_profile(
allow_reset=allow_reset,
allow_skip=True,
alignment=Alignment.CENTER,
frame=FrameProperties.min(tr("Main profile")),
frame=FrameProperties.min(tr('Main profile')),
).run()
match result.type_:

View File

@ -46,13 +46,13 @@ class ProfileHandler:
if profile is not None:
data = {
"main": profile.name,
"details": [profile.name for profile in profile.current_selection],
"custom_settings": {profile.name: profile.custom_settings for profile in profile.current_selection},
'main': profile.name,
'details': [profile.name 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:
data["path"] = self._url_path
data['path'] = self._url_path
return data
@ -66,7 +66,7 @@ class ProfileHandler:
# load all the default_profiles from url and custom
# so that we can then apply whatever was specified
# 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
local_path = Path(url_path)
@ -100,7 +100,7 @@ class ProfileHandler:
# if custom_profile := self.get_profile_by_name('Custom'):
# 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
if not profile:
@ -108,14 +108,14 @@ class ProfileHandler:
valid_sub_profiles: list[Profile] = []
invalid_sub_profiles: list[str] = []
details: list[str] = profile_config.get("details", [])
details: list[str] = profile_config.get('details', [])
if details:
for detail in filter(None, details):
# [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
if detail == "Kde":
detail = "KDE Plasma"
if detail == 'Kde':
detail = 'KDE Plasma'
if sub_profile := self.get_profile_by_name(detail):
valid_sub_profiles.append(sub_profile)
@ -123,9 +123,9 @@ class ProfileHandler:
invalid_sub_profiles.append(detail)
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
for sub_profile in valid_sub_profiles:
@ -182,28 +182,28 @@ class ProfileHandler:
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]
def install_greeter(self, install_session: "Installer", greeter: GreeterType) -> None:
def install_greeter(self, install_session: 'Installer', greeter: GreeterType) -> None:
packages = []
service = None
match greeter:
case GreeterType.LightdmSlick:
packages = ["lightdm", "lightdm-slick-greeter"]
service = ["lightdm"]
packages = ['lightdm', 'lightdm-slick-greeter']
service = ['lightdm']
case GreeterType.Lightdm:
packages = ["lightdm", "lightdm-gtk-greeter"]
service = ["lightdm"]
packages = ['lightdm', 'lightdm-gtk-greeter']
service = ['lightdm']
case GreeterType.Sddm:
packages = ["sddm"]
service = ["sddm"]
packages = ['sddm']
service = ['sddm']
case GreeterType.Gdm:
packages = ["gdm"]
service = ["gdm"]
packages = ['gdm']
service = ['gdm']
case GreeterType.Ly:
packages = ["ly"]
service = ["ly"]
packages = ['ly']
service = ['ly']
case GreeterType.CosmicSession:
packages = ["cosmic-greeter"]
packages = ['cosmic-greeter']
if packages:
install_session.add_additional_packages(packages)
@ -212,35 +212,35 @@ class ProfileHandler:
# slick-greeter requires a config change
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:
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)
def install_gfx_driver(self, install_session: "Installer", driver: GfxDriver) -> None:
debug(f"Installing GFX driver: {driver.value}")
def install_gfx_driver(self, install_session: 'Installer', driver: GfxDriver) -> None:
debug(f'Installing GFX driver: {driver.value}')
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
install_session.add_additional_packages(headers)
elif driver in [GfxDriver.AllOpenSource, GfxDriver.AmdOpenSource]:
# The order of these two are important if amdgpu is installed #808
install_session.remove_mod("amdgpu")
install_session.remove_mod("radeon")
install_session.remove_mod('amdgpu')
install_session.remove_mod('radeon')
install_session.append_mod("amdgpu")
install_session.append_mod("radeon")
install_session.append_mod('amdgpu')
install_session.append_mod('radeon')
driver_pkgs = driver.gfx_packages()
pkg_names = [p.value for p in driver_pkgs]
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
if not profile:
@ -260,9 +260,9 @@ class ProfileHandler:
"""
try:
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)
filepath = Path(fp.name)
@ -270,7 +270,7 @@ class ProfileHandler:
self.remove_custom_profiles(profiles)
self.add_custom_profiles(profiles)
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)
def _load_profile_class(self, module: ModuleType) -> list[Profile]:
@ -288,7 +288,7 @@ class ProfileHandler:
if isinstance(cls_, Profile):
profiles.append(cls_)
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
@ -301,7 +301,7 @@ class ProfileHandler:
duplicates = [x for x in counter.items() if x[1] != 1]
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)
sys.exit(1)
@ -312,7 +312,7 @@ class ProfileHandler:
"""
with open(file) as fp:
for line in fp.readlines():
if "__packages__" in line:
if '__packages__' in line:
return True
return False
@ -321,15 +321,15 @@ class ProfileHandler:
Process a file for profile definitions
"""
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 []
if not file.is_file():
info(f"Cannot find profile file {file}")
info(f'Cannot find profile file {file}')
return []
name = file.name.removesuffix(file.suffix)
debug(f"Importing profile: {file}")
debug(f'Importing profile: {file}')
try:
if spec := importlib.util.spec_from_file_location(name, file):
@ -338,7 +338,7 @@ class ProfileHandler:
spec.loader.exec_module(imported)
return self._load_profile_class(imported)
except Exception as e:
error(f"Unable to parse file {file}: {e}")
error(f'Unable to parse file {file}: {e}')
return []
@ -346,11 +346,11 @@ class ProfileHandler:
"""
Search the profile path for profile definitions
"""
profiles_path = Path(__file__).parents[2] / "default_profiles"
profiles_path = Path(__file__).parents[2] / 'default_profiles'
profiles = []
for file in profiles_path.glob("**/*.py"):
for file in profiles_path.glob('**/*.py'):
# ignore the abstract default_profiles class
if "profile.py" in file.name:
if 'profile.py' in file.name:
continue
profiles += self._process_profile_file(file)

View File

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

View File

@ -20,7 +20,7 @@ class Language:
@property
def display_name(self) -> str:
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:
if self.name_en == lang_or_translated_lang:
@ -35,8 +35,8 @@ class Language:
class TranslationHandler:
def __init__(self) -> None:
self._base_pot = "base.pot"
self._languages = "languages.json"
self._base_pot = 'base.pot'
self._languages = 'languages.json'
self._total_messages = self._get_total_active_messages()
self._translated_languages = self._get_translations()
@ -55,17 +55,17 @@ class TranslationHandler:
languages = []
for short_form in defined_languages:
mapping_entry: dict[str, str] = next(filter(lambda x: x["abbr"] == short_form, mappings))
abbr = mapping_entry["abbr"]
lang = mapping_entry["lang"]
translated_lang = mapping_entry.get("translated_lang", None)
mapping_entry: dict[str, str] = next(filter(lambda x: x['abbr'] == short_form, mappings))
abbr = mapping_entry['abbr']
lang = mapping_entry['lang']
translated_lang = mapping_entry.get('translated_lang', None)
try:
# 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
if abbr == "en":
if abbr == 'en':
percent = 100
else:
num_translations = self._get_catalog_size(translation)
@ -105,9 +105,9 @@ class TranslationHandler:
Get total messages that could be translated
"""
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()
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
@ -118,7 +118,7 @@ class TranslationHandler:
try:
return next(filter(lambda x: x.name_en == name, self._translated_languages))
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:
"""
@ -141,7 +141,7 @@ class TranslationHandler:
Get the locales directory path
"""
cur_path = Path(__file__).parent.parent
locales_dir = Path.joinpath(cur_path, "locales")
locales_dir = Path.joinpath(cur_path, 'locales')
return locales_dir
def _provided_translations(self) -> list[str]:
@ -153,7 +153,7 @@ class TranslationHandler:
translation_files = []
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)
return translation_files

View File

@ -4,7 +4,7 @@ from functools import lru_cache
@lru_cache(maxsize=128)
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:
@ -12,7 +12,7 @@ def _count_wchars(string: str) -> int:
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.
>>> unicode_ljust('Hello', 15, '*')
'Hello**********'
@ -26,7 +26,7 @@ def unicode_ljust(string: str, width: int, fillbyte: str = " ") -> str:
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.
>>> unicode_rjust('Hello', 15, '*')
'**********Hello'

View File

@ -20,7 +20,7 @@ def get_password(
while True:
user_hdr = 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:
user_hdr = header
@ -42,12 +42,12 @@ def get_password(
return password
if header is not None:
confirmation_header = f"{header}{tr('Password')}: {password.hidden()}\n"
confirmation_header = f'{header}{tr("Password")}: {password.hidden()}\n'
else:
confirmation_header = f"{tr('Password')}: {password.hidden()}\n"
confirmation_header = f'{tr("Password")}: {password.hidden()}\n'
result = EditMenu(
tr("Confirm password"),
tr('Confirm password'),
header=confirmation_header,
alignment=Alignment.CENTER,
allow_skip=False,
@ -57,7 +57,7 @@ def get_password(
if password._plaintext == result.text():
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(
@ -73,7 +73,7 @@ def prompt_dir(
if dest_path.exists() and dest_path.is_dir():
return None
return tr("Not a valid directory")
return tr('Not a valid directory')
if validate:
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:
if header:
text = f"{header}:\n"
text = f'{header}:\n'
else:
text = ""
text = ''
nr_items = len(items)
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)
# 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

View File

@ -31,7 +31,7 @@ def ask_user_questions() -> None:
global_menu = GlobalMenu(arch_config_handler.config)
if not arch_config_handler.args.advanced:
global_menu.set_enabled("parallel_downloads", False)
global_menu.set_enabled('parallel_downloads', False)
global_menu.run()
@ -42,12 +42,12 @@ def perform_installation(mountpoint: Path) -> None:
Only requirement is that the block devices are
formatted and setup prior to entering this function.
"""
info("Starting installation...")
info('Starting installation...')
config = arch_config_handler.config
if not config.disk_config:
error("No disk configuration provided")
error('No disk configuration provided')
return
disk_config = config.disk_config
@ -88,10 +88,10 @@ def perform_installation(mountpoint: Path) -> None:
installation.set_mirrors(mirror_config, on_target=True)
if config.swap:
installation.setup_swap("zram")
installation.setup_swap('zram')
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)
@ -112,9 +112,9 @@ def perform_installation(mountpoint: Path) -> None:
if audio_config:
audio_config.install_audio_config(installation)
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)
if profile_config := config.profile_config:
@ -130,7 +130,7 @@ def perform_installation(mountpoint: Path) -> None:
installation.enable_espeakup()
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)
if (profile_config := config.profile_config) and profile_config.profile:
@ -147,7 +147,7 @@ def perform_installation(mountpoint: Path) -> None:
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:
with Tui():
@ -157,7 +157,7 @@ def perform_installation(mountpoint: Path) -> None:
case PostInstallationAction.EXIT:
pass
case PostInstallationAction.REBOOT:
os.system("reboot")
os.system('reboot')
case PostInstallationAction.CHROOT:
try:
installation.drop_to_shell()
@ -179,7 +179,7 @@ def guided() -> None:
if not arch_config_handler.args.silent:
with Tui():
if not config.confirm_config():
debug("Installation aborted")
debug('Installation aborted')
guided()
if arch_config_handler.config.disk_config:

View File

@ -1,10 +1,10 @@
import glob
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")]:
if script.stem in ["__init__", "list"]:
for script in [Path(x) for x in glob.glob(f'{Path(__file__).parent}/*.py')]:
if script.stem in ['__init__', 'list']:
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
if not config.disk_config:
error("No disk configuration provided")
error('No disk configuration provided')
return
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
# some other minor details as specified by this profile and user.
if installation.minimal_installation():
installation.set_hostname("minimal-arch")
installation.set_hostname('minimal-arch')
installation.add_bootloader(Bootloader.Systemd)
network_config = config.network_config
@ -46,19 +46,19 @@ def perform_installation(mountpoint: Path) -> None:
config.profile_config,
)
installation.add_additional_packages(["nano", "wget", "git"])
installation.add_additional_packages(['nano', 'wget', 'git'])
profile_config = ProfileConfiguration(MinimalProfile())
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)
# Once this is done, we output some useful information to the user
# And the installation is complete.
info("There are two new accounts in your installation after reboot:")
info(" * root (password: airoot)")
info(" * devel (password: devel)")
info('There are two new accounts in your installation after reboot:')
info(' * root (password: airoot)')
info(' * devel (password: devel)')
def _minimal() -> None:
@ -82,7 +82,7 @@ def _minimal() -> None:
if not arch_config_handler.args.silent:
with Tui():
if not config.confirm_config():
debug("Installation aborted")
debug('Installation aborted')
_minimal()
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.disable_all()
global_menu.set_enabled("archinstall_language", True)
global_menu.set_enabled("disk_config", True)
global_menu.set_enabled("disk_encryption", True)
global_menu.set_enabled("swap", True)
global_menu.set_enabled("__config__", True)
global_menu.set_enabled('archinstall_language', True)
global_menu.set_enabled('disk_config', True)
global_menu.set_enabled('disk_encryption', True)
global_menu.set_enabled('swap', True)
global_menu.set_enabled('__config__', True)
global_menu.run()
@ -33,7 +33,7 @@ def perform_installation(mountpoint: Path) -> None:
config = arch_config_handler.config
if not config.disk_config:
error("No disk configuration provided")
error('No disk configuration provided')
return
disk_config = config.disk_config
@ -52,12 +52,12 @@ def perform_installation(mountpoint: Path) -> None:
installation.mount_ordered_layout()
# 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():
target.parent.mkdir(parents=True)
# 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:
@ -74,7 +74,7 @@ def _only_hd() -> None:
if not arch_config_handler.args.silent:
with Tui():
if not config.confirm_config():
debug("Installation aborted")
debug('Installation aborted')
_only_hd()
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).
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):
Tui.print(f"{i}...")
Tui.print(f'{i}...')
time.sleep(1)
install_session = storage["installation_session"]
install_session = storage['installation_session']
p.install(install_session)

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