parent
9af23218c4
commit
d3f32f308c
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import archinstall
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
archinstall.run_as_a_module()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ from enum import Enum
|
|||
|
||||
|
||||
class SeatAccess(Enum):
|
||||
seatd = "seatd"
|
||||
polkit = "polkit"
|
||||
seatd = 'seatd'
|
||||
polkit = 'polkit'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.')
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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}')
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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 :)
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()]
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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](
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from .abstract_menu import AbstractMenu, AbstractSubMenu
|
|||
from .list_manager import ListManager
|
||||
|
||||
__all__ = [
|
||||
"AbstractMenu",
|
||||
"AbstractSubMenu",
|
||||
"ListManager",
|
||||
'AbstractMenu',
|
||||
'AbstractSubMenu',
|
||||
'ListManager',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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__(
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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_:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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}')
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue