parent
9af23218c4
commit
d3f32f308c
|
|
@ -25,36 +25,36 @@ def plugin(f, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
|
|
||||||
def _log_sys_info() -> None:
|
def _log_sys_info() -> None:
|
||||||
# Log various information about hardware before starting the installation. This might assist in troubleshooting
|
# Log various information about hardware before starting the installation. This might assist in troubleshooting
|
||||||
debug(f"Hardware model detected: {SysInfo.sys_vendor()} {SysInfo.product_name()}; UEFI mode: {SysInfo.has_uefi()}")
|
debug(f'Hardware model detected: {SysInfo.sys_vendor()} {SysInfo.product_name()}; UEFI mode: {SysInfo.has_uefi()}')
|
||||||
debug(f"Processor model detected: {SysInfo.cpu_model()}")
|
debug(f'Processor model detected: {SysInfo.cpu_model()}')
|
||||||
debug(f"Memory statistics: {SysInfo.mem_available()} available out of {SysInfo.mem_total()} total installed")
|
debug(f'Memory statistics: {SysInfo.mem_available()} available out of {SysInfo.mem_total()} total installed')
|
||||||
debug(f"Virtualization detected: {SysInfo.virtualization()}; is VM: {SysInfo.is_vm()}")
|
debug(f'Virtualization detected: {SysInfo.virtualization()}; is VM: {SysInfo.is_vm()}')
|
||||||
debug(f"Graphics devices detected: {SysInfo._graphics_devices().keys()}")
|
debug(f'Graphics devices detected: {SysInfo._graphics_devices().keys()}')
|
||||||
|
|
||||||
# For support reasons, we'll log the disk layout pre installation to match against post-installation layout
|
# For support reasons, we'll log the disk layout pre installation to match against post-installation layout
|
||||||
debug(f"Disk states before installing:\n{disk_layouts()}")
|
debug(f'Disk states before installing:\n{disk_layouts()}')
|
||||||
|
|
||||||
|
|
||||||
def _fetch_arch_db() -> None:
|
def _fetch_arch_db() -> None:
|
||||||
info("Fetching Arch Linux package database...")
|
info('Fetching Arch Linux package database...')
|
||||||
try:
|
try:
|
||||||
Pacman.run("-Sy")
|
Pacman.run('-Sy')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug(f"Failed to sync Arch Linux package database: {e}")
|
debug(f'Failed to sync Arch Linux package database: {e}')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def _check_new_version() -> None:
|
def _check_new_version() -> None:
|
||||||
info("Checking version...")
|
info('Checking version...')
|
||||||
upgrade = None
|
upgrade = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
upgrade = Pacman.run("-Qu archinstall").decode()
|
upgrade = Pacman.run('-Qu archinstall').decode()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug(f"Failed determine pacman version: {e}")
|
debug(f'Failed determine pacman version: {e}')
|
||||||
|
|
||||||
if upgrade:
|
if upgrade:
|
||||||
text = f"New version available: {upgrade}"
|
text = f'New version available: {upgrade}'
|
||||||
info(text)
|
info(text)
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
|
|
@ -65,12 +65,12 @@ def main() -> int:
|
||||||
OR straight as a module: python -m archinstall
|
OR straight as a module: python -m archinstall
|
||||||
In any case we will be attempting to load the provided script to be run from the scripts/ folder
|
In any case we will be attempting to load the provided script to be run from the scripts/ folder
|
||||||
"""
|
"""
|
||||||
if "--help" in sys.argv or "-h" in sys.argv:
|
if '--help' in sys.argv or '-h' in sys.argv:
|
||||||
arch_config_handler.print_help()
|
arch_config_handler.print_help()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if os.getuid() != 0:
|
if os.getuid() != 0:
|
||||||
print(tr("Archinstall requires root privileges to run. See --help for more."))
|
print(tr('Archinstall requires root privileges to run. See --help for more.'))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
_log_sys_info()
|
_log_sys_info()
|
||||||
|
|
@ -83,7 +83,7 @@ def main() -> int:
|
||||||
|
|
||||||
script = arch_config_handler.args.script
|
script = arch_config_handler.args.script
|
||||||
|
|
||||||
mod_name = f"archinstall.scripts.{script}"
|
mod_name = f'archinstall.scripts.{script}'
|
||||||
# by loading the module we'll automatically run the script
|
# by loading the module we'll automatically run the script
|
||||||
importlib.import_module(mod_name)
|
importlib.import_module(mod_name)
|
||||||
|
|
||||||
|
|
@ -103,11 +103,11 @@ def run_as_a_module() -> None:
|
||||||
Tui.shutdown()
|
Tui.shutdown()
|
||||||
|
|
||||||
if exc:
|
if exc:
|
||||||
err = "".join(traceback.format_exception(exc))
|
err = ''.join(traceback.format_exception(exc))
|
||||||
error(err)
|
error(err)
|
||||||
|
|
||||||
text = (
|
text = (
|
||||||
"Archinstall experienced the above error. If you think this is a bug, please report it to\n"
|
'Archinstall experienced the above error. If you think this is a bug, please report it to\n'
|
||||||
'https://github.com/archlinux/archinstall and include the log file "/var/log/archinstall/install.log".\n\n'
|
'https://github.com/archlinux/archinstall and include the log file "/var/log/archinstall/install.log".\n\n'
|
||||||
"Hint: To extract the log from a live ISO \ncurl -F'file=@/var/log/archinstall/install.log' https://0x0.st\n"
|
"Hint: To extract the log from a live ISO \ncurl -F'file=@/var/log/archinstall/install.log' https://0x0.st\n"
|
||||||
)
|
)
|
||||||
|
|
@ -119,19 +119,19 @@ def run_as_a_module() -> None:
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"FormattedOutput",
|
'FormattedOutput',
|
||||||
"Language",
|
'Language',
|
||||||
"Pacman",
|
'Pacman',
|
||||||
"SysInfo",
|
'SysInfo',
|
||||||
"Tui",
|
'Tui',
|
||||||
"arch_config_handler",
|
'arch_config_handler',
|
||||||
"debug",
|
'debug',
|
||||||
"disk_layouts",
|
'disk_layouts',
|
||||||
"error",
|
'error',
|
||||||
"info",
|
'info',
|
||||||
"load_plugin",
|
'load_plugin',
|
||||||
"log",
|
'log',
|
||||||
"plugin",
|
'plugin',
|
||||||
"translation_handler",
|
'translation_handler',
|
||||||
"warn",
|
'warn',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import archinstall
|
import archinstall
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
archinstall.run_as_a_module()
|
archinstall.run_as_a_module()
|
||||||
|
|
|
||||||
|
|
@ -9,22 +9,22 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class PipewireProfile(Profile):
|
class PipewireProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Pipewire", ProfileType.Application)
|
super().__init__('Pipewire', ProfileType.Application)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"pipewire",
|
'pipewire',
|
||||||
"pipewire-alsa",
|
'pipewire-alsa',
|
||||||
"pipewire-jack",
|
'pipewire-jack',
|
||||||
"pipewire-pulse",
|
'pipewire-pulse',
|
||||||
"gst-plugin-pipewire",
|
'gst-plugin-pipewire',
|
||||||
"libpulse",
|
'libpulse',
|
||||||
"wireplumber",
|
'wireplumber',
|
||||||
]
|
]
|
||||||
|
|
||||||
def _enable_pipewire_for_all(self, install_session: "Installer") -> None:
|
def _enable_pipewire_for_all(self, install_session: 'Installer') -> None:
|
||||||
from archinstall.lib.args import arch_config_handler
|
from archinstall.lib.args import arch_config_handler
|
||||||
|
|
||||||
users: list[User] | None = arch_config_handler.config.users
|
users: list[User] | None = arch_config_handler.config.users
|
||||||
|
|
@ -34,24 +34,24 @@ class PipewireProfile(Profile):
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
# Create the full path for enabling the pipewire systemd items
|
# Create the full path for enabling the pipewire systemd items
|
||||||
service_dir = install_session.target / "home" / user.username / ".config" / "systemd" / "user" / "default.target.wants"
|
service_dir = install_session.target / 'home' / user.username / '.config' / 'systemd' / 'user' / 'default.target.wants'
|
||||||
service_dir.mkdir(parents=True, exist_ok=True)
|
service_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Set ownership of the entire user catalogue
|
# Set ownership of the entire user catalogue
|
||||||
install_session.arch_chroot(f"chown -R {user.username}:{user.username} /home/{user.username}")
|
install_session.arch_chroot(f'chown -R {user.username}:{user.username} /home/{user.username}')
|
||||||
|
|
||||||
# symlink in the correct pipewire systemd items
|
# symlink in the correct pipewire systemd items
|
||||||
install_session.arch_chroot(
|
install_session.arch_chroot(
|
||||||
f"ln -sf /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service",
|
f'ln -sf /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service',
|
||||||
run_as=user.username,
|
run_as=user.username,
|
||||||
)
|
)
|
||||||
install_session.arch_chroot(
|
install_session.arch_chroot(
|
||||||
f"ln -sf /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket",
|
f'ln -sf /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket',
|
||||||
run_as=user.username,
|
run_as=user.username,
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def install(self, install_session: "Installer") -> None:
|
def install(self, install_session: 'Installer') -> None:
|
||||||
super().install(install_session)
|
super().install(install_session)
|
||||||
install_session.add_additional_packages(self.packages)
|
install_session.add_additional_packages(self.packages)
|
||||||
self._enable_pipewire_for_all(install_session)
|
self._enable_pipewire_for_all(install_session)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
||||||
class DesktopProfile(Profile):
|
class DesktopProfile(Profile):
|
||||||
def __init__(self, current_selection: list[Profile] = []) -> None:
|
def __init__(self, current_selection: list[Profile] = []) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Desktop",
|
'Desktop',
|
||||||
ProfileType.Desktop,
|
ProfileType.Desktop,
|
||||||
current_selection=current_selection,
|
current_selection=current_selection,
|
||||||
support_greeter=True,
|
support_greeter=True,
|
||||||
|
|
@ -25,16 +25,16 @@ class DesktopProfile(Profile):
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"nano",
|
'nano',
|
||||||
"vim",
|
'vim',
|
||||||
"openssh",
|
'openssh',
|
||||||
"htop",
|
'htop',
|
||||||
"wget",
|
'wget',
|
||||||
"iwd",
|
'iwd',
|
||||||
"wireless_tools",
|
'wireless_tools',
|
||||||
"wpa_supplicant",
|
'wpa_supplicant',
|
||||||
"smartmontools",
|
'smartmontools',
|
||||||
"xdg-utils",
|
'xdg-utils',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -75,8 +75,8 @@ class DesktopProfile(Profile):
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
preview_style=PreviewStyle.RIGHT,
|
preview_style=PreviewStyle.RIGHT,
|
||||||
preview_size="auto",
|
preview_size='auto',
|
||||||
preview_frame=FrameProperties.max("Info"),
|
preview_frame=FrameProperties.max('Info'),
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
match result.type_:
|
match result.type_:
|
||||||
|
|
@ -90,17 +90,17 @@ class DesktopProfile(Profile):
|
||||||
return SelectResult.ResetCurrent
|
return SelectResult.ResetCurrent
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def post_install(self, install_session: "Installer") -> None:
|
def post_install(self, install_session: 'Installer') -> None:
|
||||||
for profile in self.current_selection:
|
for profile in self.current_selection:
|
||||||
profile.post_install(install_session)
|
profile.post_install(install_session)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def install(self, install_session: "Installer") -> None:
|
def install(self, install_session: 'Installer') -> None:
|
||||||
# Install common packages for all desktop environments
|
# Install common packages for all desktop environments
|
||||||
install_session.add_additional_packages(self.packages)
|
install_session.add_additional_packages(self.packages)
|
||||||
|
|
||||||
for profile in self.current_selection:
|
for profile in self.current_selection:
|
||||||
info(f"Installing profile {profile.name}...")
|
info(f'Installing profile {profile.name}...')
|
||||||
|
|
||||||
install_session.add_additional_packages(profile.packages)
|
install_session.add_additional_packages(profile.packages)
|
||||||
install_session.enable_service(profile.services)
|
install_session.enable_service(profile.services)
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@ from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class SeatAccess(Enum):
|
class SeatAccess(Enum):
|
||||||
seatd = "seatd"
|
seatd = 'seatd'
|
||||||
polkit = "polkit"
|
polkit = 'polkit'
|
||||||
|
|
|
||||||
|
|
@ -9,56 +9,56 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class AwesomeProfile(XorgProfile):
|
class AwesomeProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Awesome", ProfileType.WindowMgr)
|
super().__init__('Awesome', ProfileType.WindowMgr)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return super().packages + [
|
return super().packages + [
|
||||||
"awesome",
|
'awesome',
|
||||||
"alacritty",
|
'alacritty',
|
||||||
"xorg-xinit",
|
'xorg-xinit',
|
||||||
"xorg-xrandr",
|
'xorg-xrandr',
|
||||||
"xterm",
|
'xterm',
|
||||||
"feh",
|
'feh',
|
||||||
"slock",
|
'slock',
|
||||||
"terminus-font",
|
'terminus-font',
|
||||||
"gnu-free-fonts",
|
'gnu-free-fonts',
|
||||||
"ttf-liberation",
|
'ttf-liberation',
|
||||||
"xsel",
|
'xsel',
|
||||||
]
|
]
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def install(self, install_session: "Installer") -> None:
|
def install(self, install_session: 'Installer') -> None:
|
||||||
super().install(install_session)
|
super().install(install_session)
|
||||||
|
|
||||||
# TODO: Copy a full configuration to ~/.config/awesome/rc.lua instead.
|
# TODO: Copy a full configuration to ~/.config/awesome/rc.lua instead.
|
||||||
with open(f"{install_session.target}/etc/xdg/awesome/rc.lua") as fh:
|
with open(f'{install_session.target}/etc/xdg/awesome/rc.lua') as fh:
|
||||||
awesome_lua = fh.read()
|
awesome_lua = fh.read()
|
||||||
|
|
||||||
# Replace xterm with alacritty for a smoother experience.
|
# Replace xterm with alacritty for a smoother experience.
|
||||||
awesome_lua = awesome_lua.replace('"xterm"', '"alacritty"')
|
awesome_lua = awesome_lua.replace('"xterm"', '"alacritty"')
|
||||||
|
|
||||||
with open(f"{install_session.target}/etc/xdg/awesome/rc.lua", "w") as fh:
|
with open(f'{install_session.target}/etc/xdg/awesome/rc.lua', 'w') as fh:
|
||||||
fh.write(awesome_lua)
|
fh.write(awesome_lua)
|
||||||
|
|
||||||
# TODO: Configure the right-click-menu to contain the above packages that were installed. (as a user config)
|
# TODO: Configure the right-click-menu to contain the above packages that were installed. (as a user config)
|
||||||
|
|
||||||
# TODO: check if we selected a greeter,
|
# TODO: check if we selected a greeter,
|
||||||
# but for now, awesome is intended to run without one.
|
# but for now, awesome is intended to run without one.
|
||||||
with open(f"{install_session.target}/etc/X11/xinit/xinitrc") as xinitrc:
|
with open(f'{install_session.target}/etc/X11/xinit/xinitrc') as xinitrc:
|
||||||
xinitrc_data = xinitrc.read()
|
xinitrc_data = xinitrc.read()
|
||||||
|
|
||||||
for line in xinitrc_data.split("\n"):
|
for line in xinitrc_data.split('\n'):
|
||||||
if "twm &" in line:
|
if 'twm &' in line:
|
||||||
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
|
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
|
||||||
if "xclock" in line:
|
if 'xclock' in line:
|
||||||
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
|
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
|
||||||
if "xterm" in line:
|
if 'xterm' in line:
|
||||||
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
|
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
|
||||||
|
|
||||||
xinitrc_data += "\n"
|
xinitrc_data += '\n'
|
||||||
xinitrc_data += "exec awesome\n"
|
xinitrc_data += 'exec awesome\n'
|
||||||
|
|
||||||
with open(f"{install_session.target}/etc/X11/xinit/xinitrc", "w") as xinitrc:
|
with open(f'{install_session.target}/etc/X11/xinit/xinitrc', 'w') as xinitrc:
|
||||||
xinitrc.write(xinitrc_data)
|
xinitrc.write(xinitrc_data)
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,18 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class BspwmProfile(XorgProfile):
|
class BspwmProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Bspwm", ProfileType.WindowMgr)
|
super().__init__('Bspwm', ProfileType.WindowMgr)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
# return super().packages + [
|
# return super().packages + [
|
||||||
return [
|
return [
|
||||||
"bspwm",
|
'bspwm',
|
||||||
"sxhkd",
|
'sxhkd',
|
||||||
"dmenu",
|
'dmenu',
|
||||||
"xdo",
|
'xdo',
|
||||||
"rxvt-unicode",
|
'rxvt-unicode',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,17 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class BudgieProfile(XorgProfile):
|
class BudgieProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Budgie", ProfileType.DesktopEnv)
|
super().__init__('Budgie', ProfileType.DesktopEnv)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"materia-gtk-theme",
|
'materia-gtk-theme',
|
||||||
"budgie",
|
'budgie',
|
||||||
"mate-terminal",
|
'mate-terminal',
|
||||||
"nemo",
|
'nemo',
|
||||||
"papirus-icon-theme",
|
'papirus-icon-theme',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,23 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class CinnamonProfile(XorgProfile):
|
class CinnamonProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Cinnamon", ProfileType.DesktopEnv)
|
super().__init__('Cinnamon', ProfileType.DesktopEnv)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"cinnamon",
|
'cinnamon',
|
||||||
"system-config-printer",
|
'system-config-printer',
|
||||||
"gnome-keyring",
|
'gnome-keyring',
|
||||||
"gnome-terminal",
|
'gnome-terminal',
|
||||||
"blueman",
|
'blueman',
|
||||||
"bluez-utils",
|
'bluez-utils',
|
||||||
"engrampa",
|
'engrampa',
|
||||||
"gnome-screenshot",
|
'gnome-screenshot',
|
||||||
"gvfs-smb",
|
'gvfs-smb',
|
||||||
"xed",
|
'xed',
|
||||||
"xdg-user-dirs-gtk",
|
'xdg-user-dirs-gtk',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class CosmicProfile(XorgProfile):
|
class CosmicProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("cosmic-epoch", ProfileType.DesktopEnv, advanced=True)
|
super().__init__('cosmic-epoch', ProfileType.DesktopEnv, advanced=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"cosmic",
|
'cosmic',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class CutefishProfile(XorgProfile):
|
class CutefishProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Cutefish", ProfileType.DesktopEnv)
|
super().__init__('Cutefish', ProfileType.DesktopEnv)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"cutefish",
|
'cutefish',
|
||||||
"noto-fonts",
|
'noto-fonts',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,15 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class DeepinProfile(XorgProfile):
|
class DeepinProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Deepin", ProfileType.DesktopEnv)
|
super().__init__('Deepin', ProfileType.DesktopEnv)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"deepin",
|
'deepin',
|
||||||
"deepin-terminal",
|
'deepin-terminal',
|
||||||
"deepin-editor",
|
'deepin-editor',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class EnlighenmentProfile(XorgProfile):
|
class EnlighenmentProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Enlightenment", ProfileType.WindowMgr)
|
super().__init__('Enlightenment', ProfileType.WindowMgr)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"enlightenment",
|
'enlightenment',
|
||||||
"terminology",
|
'terminology',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class GnomeProfile(XorgProfile):
|
class GnomeProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("GNOME", ProfileType.DesktopEnv)
|
super().__init__('GNOME', ProfileType.DesktopEnv)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"gnome",
|
'gnome',
|
||||||
"gnome-tweaks",
|
'gnome-tweaks',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -12,26 +12,26 @@ from archinstall.tui.types import Alignment, FrameProperties
|
||||||
|
|
||||||
class HyprlandProfile(XorgProfile):
|
class HyprlandProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Hyprland", ProfileType.DesktopEnv)
|
super().__init__('Hyprland', ProfileType.DesktopEnv)
|
||||||
|
|
||||||
self.custom_settings = {"seat_access": None}
|
self.custom_settings = {'seat_access': None}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"hyprland",
|
'hyprland',
|
||||||
"dunst",
|
'dunst',
|
||||||
"kitty",
|
'kitty',
|
||||||
"uwsm",
|
'uwsm',
|
||||||
"dolphin",
|
'dolphin',
|
||||||
"wofi",
|
'wofi',
|
||||||
"xdg-desktop-portal-hyprland",
|
'xdg-desktop-portal-hyprland',
|
||||||
"qt5-wayland",
|
'qt5-wayland',
|
||||||
"qt6-wayland",
|
'qt6-wayland',
|
||||||
"polkit-kde-agent",
|
'polkit-kde-agent',
|
||||||
"grim",
|
'grim',
|
||||||
"slurp",
|
'slurp',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -42,32 +42,32 @@ class HyprlandProfile(XorgProfile):
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
if pref := self.custom_settings.get("seat_access", None):
|
if pref := self.custom_settings.get('seat_access', None):
|
||||||
return [pref]
|
return [pref]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _ask_seat_access(self) -> None:
|
def _ask_seat_access(self) -> None:
|
||||||
# need to activate seat service and add to seat group
|
# need to activate seat service and add to seat group
|
||||||
header = tr("Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)")
|
header = tr('Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)')
|
||||||
header += "\n" + tr("Choose an option to give Hyprland access to your hardware") + "\n"
|
header += '\n' + tr('Choose an option to give Hyprland access to your hardware') + '\n'
|
||||||
|
|
||||||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||||
group = MenuItemGroup(items, sort_items=True)
|
group = MenuItemGroup(items, sort_items=True)
|
||||||
|
|
||||||
default = self.custom_settings.get("seat_access", None)
|
default = self.custom_settings.get('seat_access', None)
|
||||||
group.set_default_by_value(default)
|
group.set_default_by_value(default)
|
||||||
|
|
||||||
result = SelectMenu[SeatAccess](
|
result = SelectMenu[SeatAccess](
|
||||||
group,
|
group,
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=False,
|
allow_skip=False,
|
||||||
frame=FrameProperties.min(tr("Seat access")),
|
frame=FrameProperties.min(tr('Seat access')),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
if result.type_ == ResultType.Selection:
|
if result.type_ == ResultType.Selection:
|
||||||
if result.item() is not None:
|
if result.item() is not None:
|
||||||
self.custom_settings["seat_access"] = result.get_value().value
|
self.custom_settings['seat_access'] = result.get_value().value
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def do_on_select(self) -> None:
|
def do_on_select(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,21 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class I3wmProfile(XorgProfile):
|
class I3wmProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("i3-wm", ProfileType.WindowMgr)
|
super().__init__('i3-wm', ProfileType.WindowMgr)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"i3-wm",
|
'i3-wm',
|
||||||
"i3lock",
|
'i3lock',
|
||||||
"i3status",
|
'i3status',
|
||||||
"i3blocks",
|
'i3blocks',
|
||||||
"xss-lock",
|
'xss-lock',
|
||||||
"xterm",
|
'xterm',
|
||||||
"lightdm-gtk-greeter",
|
'lightdm-gtk-greeter',
|
||||||
"lightdm",
|
'lightdm',
|
||||||
"dmenu",
|
'dmenu',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -13,22 +13,22 @@ from archinstall.tui.types import Alignment, FrameProperties
|
||||||
class LabwcProfile(XorgProfile):
|
class LabwcProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Labwc",
|
'Labwc',
|
||||||
ProfileType.WindowMgr,
|
ProfileType.WindowMgr,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.custom_settings = {"seat_access": None}
|
self.custom_settings = {'seat_access': None}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
additional = []
|
additional = []
|
||||||
if seat := self.custom_settings.get("seat_access", None):
|
if seat := self.custom_settings.get('seat_access', None):
|
||||||
additional = [seat]
|
additional = [seat]
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"alacritty",
|
'alacritty',
|
||||||
"labwc",
|
'labwc',
|
||||||
] + additional
|
] + additional
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -39,32 +39,32 @@ class LabwcProfile(XorgProfile):
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
if pref := self.custom_settings.get("seat_access", None):
|
if pref := self.custom_settings.get('seat_access', None):
|
||||||
return [pref]
|
return [pref]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _ask_seat_access(self) -> None:
|
def _ask_seat_access(self) -> None:
|
||||||
# need to activate seat service and add to seat group
|
# need to activate seat service and add to seat group
|
||||||
header = tr("labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)")
|
header = tr('labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)')
|
||||||
header += "\n" + tr("Choose an option to give labwc access to your hardware") + "\n"
|
header += '\n' + tr('Choose an option to give labwc access to your hardware') + '\n'
|
||||||
|
|
||||||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||||
group = MenuItemGroup(items, sort_items=True)
|
group = MenuItemGroup(items, sort_items=True)
|
||||||
|
|
||||||
default = self.custom_settings.get("seat_access", None)
|
default = self.custom_settings.get('seat_access', None)
|
||||||
group.set_default_by_value(default)
|
group.set_default_by_value(default)
|
||||||
|
|
||||||
result = SelectMenu[SeatAccess](
|
result = SelectMenu[SeatAccess](
|
||||||
group,
|
group,
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=False,
|
allow_skip=False,
|
||||||
frame=FrameProperties.min(tr("Seat access")),
|
frame=FrameProperties.min(tr('Seat access')),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
if result.type_ == ResultType.Selection:
|
if result.type_ == ResultType.Selection:
|
||||||
if result.item() is not None:
|
if result.item() is not None:
|
||||||
self.custom_settings["seat_access"] = result.get_value().value
|
self.custom_settings['seat_access'] = result.get_value().value
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def do_on_select(self) -> None:
|
def do_on_select(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class LxqtProfile(XorgProfile):
|
class LxqtProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Lxqt", ProfileType.DesktopEnv)
|
super().__init__('Lxqt', ProfileType.DesktopEnv)
|
||||||
|
|
||||||
# NOTE: SDDM is the only officially supported greeter for LXQt, so unlike other DEs, lightdm is not used here.
|
# NOTE: SDDM is the only officially supported greeter for LXQt, so unlike other DEs, lightdm is not used here.
|
||||||
# LXQt works with lightdm, but since this is not supported, we will not default to this.
|
# LXQt works with lightdm, but since this is not supported, we will not default to this.
|
||||||
|
|
@ -15,13 +15,13 @@ class LxqtProfile(XorgProfile):
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"lxqt",
|
'lxqt',
|
||||||
"breeze-icons",
|
'breeze-icons',
|
||||||
"oxygen-icons",
|
'oxygen-icons',
|
||||||
"xdg-utils",
|
'xdg-utils',
|
||||||
"ttf-freefont",
|
'ttf-freefont',
|
||||||
"leafpad",
|
'leafpad',
|
||||||
"slock",
|
'slock',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class MateProfile(XorgProfile):
|
class MateProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Mate", ProfileType.DesktopEnv)
|
super().__init__('Mate', ProfileType.DesktopEnv)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"mate",
|
'mate',
|
||||||
"mate-extra",
|
'mate-extra',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -13,30 +13,30 @@ from archinstall.tui.types import Alignment, FrameProperties
|
||||||
class NiriProfile(XorgProfile):
|
class NiriProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Niri",
|
'Niri',
|
||||||
ProfileType.WindowMgr,
|
ProfileType.WindowMgr,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.custom_settings = {"seat_access": None}
|
self.custom_settings = {'seat_access': None}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
additional = []
|
additional = []
|
||||||
if seat := self.custom_settings.get("seat_access", None):
|
if seat := self.custom_settings.get('seat_access', None):
|
||||||
additional = [seat]
|
additional = [seat]
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"niri",
|
'niri',
|
||||||
"alacritty",
|
'alacritty',
|
||||||
"fuzzel",
|
'fuzzel',
|
||||||
"mako",
|
'mako',
|
||||||
"xorg-xwayland",
|
'xorg-xwayland',
|
||||||
"waybar",
|
'waybar',
|
||||||
"swaybg",
|
'swaybg',
|
||||||
"swayidle",
|
'swayidle',
|
||||||
"swaylock",
|
'swaylock',
|
||||||
"xdg-desktop-portal-gnome",
|
'xdg-desktop-portal-gnome',
|
||||||
] + additional
|
] + additional
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -47,32 +47,32 @@ class NiriProfile(XorgProfile):
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
if pref := self.custom_settings.get("seat_access", None):
|
if pref := self.custom_settings.get('seat_access', None):
|
||||||
return [pref]
|
return [pref]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _ask_seat_access(self) -> None:
|
def _ask_seat_access(self) -> None:
|
||||||
# need to activate seat service and add to seat group
|
# need to activate seat service and add to seat group
|
||||||
header = tr("niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)")
|
header = tr('niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)')
|
||||||
header += "\n" + tr("Choose an option to give niri access to your hardware") + "\n"
|
header += '\n' + tr('Choose an option to give niri access to your hardware') + '\n'
|
||||||
|
|
||||||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||||
group = MenuItemGroup(items, sort_items=True)
|
group = MenuItemGroup(items, sort_items=True)
|
||||||
|
|
||||||
default = self.custom_settings.get("seat_access", None)
|
default = self.custom_settings.get('seat_access', None)
|
||||||
group.set_default_by_value(default)
|
group.set_default_by_value(default)
|
||||||
|
|
||||||
result = SelectMenu[SeatAccess](
|
result = SelectMenu[SeatAccess](
|
||||||
group,
|
group,
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=False,
|
allow_skip=False,
|
||||||
frame=FrameProperties.min(tr("Seat access")),
|
frame=FrameProperties.min(tr('Seat access')),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
if result.type_ == ResultType.Selection:
|
if result.type_ == ResultType.Selection:
|
||||||
if result.item() is not None:
|
if result.item() is not None:
|
||||||
self.custom_settings["seat_access"] = result.get_value().value
|
self.custom_settings['seat_access'] = result.get_value().value
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def do_on_select(self) -> None:
|
def do_on_select(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,18 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class PlasmaProfile(XorgProfile):
|
class PlasmaProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("KDE Plasma", ProfileType.DesktopEnv)
|
super().__init__('KDE Plasma', ProfileType.DesktopEnv)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"plasma-meta",
|
'plasma-meta',
|
||||||
"konsole",
|
'konsole',
|
||||||
"kate",
|
'kate',
|
||||||
"dolphin",
|
'dolphin',
|
||||||
"ark",
|
'ark',
|
||||||
"plasma-workspace",
|
'plasma-workspace',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class QtileProfile(XorgProfile):
|
class QtileProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Qtile", ProfileType.WindowMgr)
|
super().__init__('Qtile', ProfileType.WindowMgr)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"qtile",
|
'qtile',
|
||||||
"alacritty",
|
'alacritty',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,15 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class RiverProfile(XorgProfile):
|
class RiverProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("River", ProfileType.WindowMgr)
|
super().__init__('River', ProfileType.WindowMgr)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"foot",
|
'foot',
|
||||||
"xdg-desktop-portal-wlr",
|
'xdg-desktop-portal-wlr',
|
||||||
"river",
|
'river',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -13,32 +13,32 @@ from archinstall.tui.types import Alignment, FrameProperties
|
||||||
class SwayProfile(XorgProfile):
|
class SwayProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Sway",
|
'Sway',
|
||||||
ProfileType.WindowMgr,
|
ProfileType.WindowMgr,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.custom_settings = {"seat_access": None}
|
self.custom_settings = {'seat_access': None}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
additional = []
|
additional = []
|
||||||
if seat := self.custom_settings.get("seat_access", None):
|
if seat := self.custom_settings.get('seat_access', None):
|
||||||
additional = [seat]
|
additional = [seat]
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"sway",
|
'sway',
|
||||||
"swaybg",
|
'swaybg',
|
||||||
"swaylock",
|
'swaylock',
|
||||||
"swayidle",
|
'swayidle',
|
||||||
"waybar",
|
'waybar',
|
||||||
"wmenu",
|
'wmenu',
|
||||||
"brightnessctl",
|
'brightnessctl',
|
||||||
"grim",
|
'grim',
|
||||||
"slurp",
|
'slurp',
|
||||||
"pavucontrol",
|
'pavucontrol',
|
||||||
"foot",
|
'foot',
|
||||||
"xorg-xwayland",
|
'xorg-xwayland',
|
||||||
] + additional
|
] + additional
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -49,32 +49,32 @@ class SwayProfile(XorgProfile):
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
if pref := self.custom_settings.get("seat_access", None):
|
if pref := self.custom_settings.get('seat_access', None):
|
||||||
return [pref]
|
return [pref]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _ask_seat_access(self) -> None:
|
def _ask_seat_access(self) -> None:
|
||||||
# need to activate seat service and add to seat group
|
# need to activate seat service and add to seat group
|
||||||
header = tr("Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)")
|
header = tr('Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)')
|
||||||
header += "\n" + tr("Choose an option to give Sway access to your hardware") + "\n"
|
header += '\n' + tr('Choose an option to give Sway access to your hardware') + '\n'
|
||||||
|
|
||||||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||||
group = MenuItemGroup(items, sort_items=True)
|
group = MenuItemGroup(items, sort_items=True)
|
||||||
|
|
||||||
default = self.custom_settings.get("seat_access", None)
|
default = self.custom_settings.get('seat_access', None)
|
||||||
group.set_default_by_value(default)
|
group.set_default_by_value(default)
|
||||||
|
|
||||||
result = SelectMenu[SeatAccess](
|
result = SelectMenu[SeatAccess](
|
||||||
group,
|
group,
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=False,
|
allow_skip=False,
|
||||||
frame=FrameProperties.min(tr("Seat access")),
|
frame=FrameProperties.min(tr('Seat access')),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
if result.type_ == ResultType.Selection:
|
if result.type_ == ResultType.Selection:
|
||||||
if result.item() is not None:
|
if result.item() is not None:
|
||||||
self.custom_settings["seat_access"] = result.get_value().value
|
self.custom_settings['seat_access'] = result.get_value().value
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def do_on_select(self) -> None:
|
def do_on_select(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,17 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class Xfce4Profile(XorgProfile):
|
class Xfce4Profile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Xfce4", ProfileType.DesktopEnv)
|
super().__init__('Xfce4', ProfileType.DesktopEnv)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"xfce4",
|
'xfce4',
|
||||||
"xfce4-goodies",
|
'xfce4-goodies',
|
||||||
"pavucontrol",
|
'pavucontrol',
|
||||||
"gvfs",
|
'gvfs',
|
||||||
"xarchiver",
|
'xarchiver',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,17 @@ from archinstall.default_profiles.xorg import XorgProfile
|
||||||
|
|
||||||
class XmonadProfile(XorgProfile):
|
class XmonadProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("Xmonad", ProfileType.WindowMgr)
|
super().__init__('Xmonad', ProfileType.WindowMgr)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"xmonad",
|
'xmonad',
|
||||||
"xmonad-contrib",
|
'xmonad-contrib',
|
||||||
"xmonad-extra",
|
'xmonad-extra',
|
||||||
"xterm",
|
'xterm',
|
||||||
"dmenu",
|
'dmenu',
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,6 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
||||||
class MinimalProfile(Profile):
|
class MinimalProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Minimal",
|
'Minimal',
|
||||||
ProfileType.Minimal,
|
ProfileType.Minimal,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,31 +12,31 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class ProfileType(Enum):
|
class ProfileType(Enum):
|
||||||
# top level default_profiles
|
# top level default_profiles
|
||||||
Server = "Server"
|
Server = 'Server'
|
||||||
Desktop = "Desktop"
|
Desktop = 'Desktop'
|
||||||
Xorg = "Xorg"
|
Xorg = 'Xorg'
|
||||||
Minimal = "Minimal"
|
Minimal = 'Minimal'
|
||||||
Custom = "Custom"
|
Custom = 'Custom'
|
||||||
# detailed selection default_profiles
|
# detailed selection default_profiles
|
||||||
ServerType = "ServerType"
|
ServerType = 'ServerType'
|
||||||
WindowMgr = "Window Manager"
|
WindowMgr = 'Window Manager'
|
||||||
DesktopEnv = "Desktop Environment"
|
DesktopEnv = 'Desktop Environment'
|
||||||
CustomType = "CustomType"
|
CustomType = 'CustomType'
|
||||||
# special things
|
# special things
|
||||||
Tailored = "Tailored"
|
Tailored = 'Tailored'
|
||||||
Application = "Application"
|
Application = 'Application'
|
||||||
|
|
||||||
|
|
||||||
class GreeterType(Enum):
|
class GreeterType(Enum):
|
||||||
Lightdm = "lightdm-gtk-greeter"
|
Lightdm = 'lightdm-gtk-greeter'
|
||||||
LightdmSlick = "lightdm-slick-greeter"
|
LightdmSlick = 'lightdm-slick-greeter'
|
||||||
Sddm = "sddm"
|
Sddm = 'sddm'
|
||||||
Gdm = "gdm"
|
Gdm = 'gdm'
|
||||||
Ly = "ly"
|
Ly = 'ly'
|
||||||
|
|
||||||
# .. todo:: Remove when we un-hide cosmic behind --advanced
|
# .. todo:: Remove when we un-hide cosmic behind --advanced
|
||||||
if "--advanced" in sys.argv:
|
if '--advanced' in sys.argv:
|
||||||
CosmicSession = "cosmic-greeter"
|
CosmicSession = 'cosmic-greeter'
|
||||||
|
|
||||||
|
|
||||||
class SelectResult(Enum):
|
class SelectResult(Enum):
|
||||||
|
|
@ -106,12 +106,12 @@ class Profile:
|
||||||
|
|
||||||
return self.advanced is False or arch_config_handler.args.advanced is True
|
return self.advanced is False or arch_config_handler.args.advanced is True
|
||||||
|
|
||||||
def install(self, install_session: "Installer") -> None:
|
def install(self, install_session: 'Installer') -> None:
|
||||||
"""
|
"""
|
||||||
Performs installation steps when this profile was selected
|
Performs installation steps when this profile was selected
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post_install(self, install_session: "Installer") -> None:
|
def post_install(self, install_session: 'Installer') -> None:
|
||||||
"""
|
"""
|
||||||
Hook that will be called when the installation process is
|
Hook that will be called when the installation process is
|
||||||
finished and custom installation steps for specific default_profiles
|
finished and custom installation steps for specific default_profiles
|
||||||
|
|
@ -196,9 +196,9 @@ class Profile:
|
||||||
if sub_profile.packages:
|
if sub_profile.packages:
|
||||||
packages.update(sub_profile.packages)
|
packages.update(sub_profile.packages)
|
||||||
|
|
||||||
text = tr("Installed packages") + ":\n"
|
text = tr('Installed packages') + ':\n'
|
||||||
|
|
||||||
for pkg in sorted(packages):
|
for pkg in sorted(packages):
|
||||||
text += f"\t- {pkg}\n"
|
text += f'\t- {pkg}\n'
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
||||||
class ServerProfile(Profile):
|
class ServerProfile(Profile):
|
||||||
def __init__(self, current_value: list[Profile] = []):
|
def __init__(self, current_value: list[Profile] = []):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Server",
|
'Server',
|
||||||
ProfileType.Server,
|
ProfileType.Server,
|
||||||
current_selection=current_value,
|
current_selection=current_value,
|
||||||
)
|
)
|
||||||
|
|
@ -39,8 +39,8 @@ class ServerProfile(Profile):
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
preview_style=PreviewStyle.RIGHT,
|
preview_style=PreviewStyle.RIGHT,
|
||||||
preview_size="auto",
|
preview_size='auto',
|
||||||
preview_frame=FrameProperties.max("Info"),
|
preview_frame=FrameProperties.max('Info'),
|
||||||
multi=True,
|
multi=True,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -55,20 +55,20 @@ class ServerProfile(Profile):
|
||||||
return SelectResult.ResetCurrent
|
return SelectResult.ResetCurrent
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def post_install(self, install_session: "Installer") -> None:
|
def post_install(self, install_session: 'Installer') -> None:
|
||||||
for profile in self.current_selection:
|
for profile in self.current_selection:
|
||||||
profile.post_install(install_session)
|
profile.post_install(install_session)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def install(self, install_session: "Installer") -> None:
|
def install(self, install_session: 'Installer') -> None:
|
||||||
server_info = self.current_selection_names()
|
server_info = self.current_selection_names()
|
||||||
details = ", ".join(server_info)
|
details = ', '.join(server_info)
|
||||||
info(f"Now installing the selected servers: {details}")
|
info(f'Now installing the selected servers: {details}')
|
||||||
|
|
||||||
for server in self.current_selection:
|
for server in self.current_selection:
|
||||||
info(f"Installing {server.name}...")
|
info(f'Installing {server.name}...')
|
||||||
install_session.add_additional_packages(server.packages)
|
install_session.add_additional_packages(server.packages)
|
||||||
install_session.enable_service(server.services)
|
install_session.enable_service(server.services)
|
||||||
server.install(install_session)
|
server.install(install_session)
|
||||||
|
|
||||||
info("If your selections included multiple servers with the same port, you may have to reconfigure them.")
|
info('If your selections included multiple servers with the same port, you may have to reconfigure them.')
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
||||||
class CockpitProfile(Profile):
|
class CockpitProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Cockpit",
|
'Cockpit',
|
||||||
ProfileType.ServerType,
|
ProfileType.ServerType,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return ["cockpit", "udisks2", "packagekit"]
|
return ['cockpit', 'udisks2', 'packagekit']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
return ["cockpit.socket"]
|
return ['cockpit.socket']
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,23 @@ if TYPE_CHECKING:
|
||||||
class DockerProfile(Profile):
|
class DockerProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Docker",
|
'Docker',
|
||||||
ProfileType.ServerType,
|
ProfileType.ServerType,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return ["docker"]
|
return ['docker']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
return ["docker"]
|
return ['docker']
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def post_install(self, install_session: "Installer") -> None:
|
def post_install(self, install_session: 'Installer') -> None:
|
||||||
from archinstall.lib.args import arch_config_handler
|
from archinstall.lib.args import arch_config_handler
|
||||||
|
|
||||||
for user in arch_config_handler.config.users:
|
for user in arch_config_handler.config.users:
|
||||||
install_session.arch_chroot(f"usermod -a -G docker {user.username}")
|
install_session.arch_chroot(f'usermod -a -G docker {user.username}')
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
||||||
class HttpdProfile(Profile):
|
class HttpdProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"httpd",
|
'httpd',
|
||||||
ProfileType.ServerType,
|
ProfileType.ServerType,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return ["apache"]
|
return ['apache']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
return ["httpd"]
|
return ['httpd']
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
||||||
class LighttpdProfile(Profile):
|
class LighttpdProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Lighttpd",
|
'Lighttpd',
|
||||||
ProfileType.ServerType,
|
ProfileType.ServerType,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return ["lighttpd"]
|
return ['lighttpd']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
return ["lighttpd"]
|
return ['lighttpd']
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,20 @@ if TYPE_CHECKING:
|
||||||
class MariadbProfile(Profile):
|
class MariadbProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Mariadb",
|
'Mariadb',
|
||||||
ProfileType.ServerType,
|
ProfileType.ServerType,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return ["mariadb"]
|
return ['mariadb']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
return ["mariadb"]
|
return ['mariadb']
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def post_install(self, install_session: "Installer") -> None:
|
def post_install(self, install_session: 'Installer') -> None:
|
||||||
install_session.arch_chroot("mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql")
|
install_session.arch_chroot('mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql')
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
||||||
class NginxProfile(Profile):
|
class NginxProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Nginx",
|
'Nginx',
|
||||||
ProfileType.ServerType,
|
ProfileType.ServerType,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return ["nginx"]
|
return ['nginx']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
return ["nginx"]
|
return ['nginx']
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,20 @@ if TYPE_CHECKING:
|
||||||
class PostgresqlProfile(Profile):
|
class PostgresqlProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Postgresql",
|
'Postgresql',
|
||||||
ProfileType.ServerType,
|
ProfileType.ServerType,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return ["postgresql"]
|
return ['postgresql']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
return ["postgresql"]
|
return ['postgresql']
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def post_install(self, install_session: "Installer") -> None:
|
def post_install(self, install_session: 'Installer') -> None:
|
||||||
install_session.arch_chroot("initdb -D /var/lib/postgres/data", run_as="postgres")
|
install_session.arch_chroot('initdb -D /var/lib/postgres/data', run_as='postgres')
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
||||||
class SshdProfile(Profile):
|
class SshdProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"sshd",
|
'sshd',
|
||||||
ProfileType.ServerType,
|
ProfileType.ServerType,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return ["openssh"]
|
return ['openssh']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
return ["sshd"]
|
return ['sshd']
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
||||||
class TomcatProfile(Profile):
|
class TomcatProfile(Profile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Tomcat",
|
'Tomcat',
|
||||||
ProfileType.ServerType,
|
ProfileType.ServerType,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return ["tomcat10"]
|
return ['tomcat10']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def services(self) -> list[str]:
|
def services(self) -> list[str]:
|
||||||
return ["tomcat10"]
|
return ['tomcat10']
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class TailoredProfile(XorgProfile):
|
class TailoredProfile(XorgProfile):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__("52-54-00-12-34-56", ProfileType.Tailored)
|
super().__init__('52-54-00-12-34-56', ProfileType.Tailored)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return ["nano", "wget", "git"]
|
return ['nano', 'wget', 'git']
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def install(self, install_session: "Installer") -> None:
|
def install(self, install_session: 'Installer') -> None:
|
||||||
super().install(install_session)
|
super().install(install_session)
|
||||||
# do whatever you like here :)
|
# do whatever you like here :)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from archinstall.lib.translationhandler import tr
|
||||||
class XorgProfile(Profile):
|
class XorgProfile(Profile):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str = "Xorg",
|
name: str = 'Xorg',
|
||||||
profile_type: ProfileType = ProfileType.Xorg,
|
profile_type: ProfileType = ProfileType.Xorg,
|
||||||
advanced: bool = False,
|
advanced: bool = False,
|
||||||
):
|
):
|
||||||
|
|
@ -20,9 +20,9 @@ class XorgProfile(Profile):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def preview_text(self) -> str:
|
def preview_text(self) -> str:
|
||||||
text = tr("Environment type: {}").format(self.profile_type.value)
|
text = tr('Environment type: {}').format(self.profile_type.value)
|
||||||
if packages := self.packages_text():
|
if packages := self.packages_text():
|
||||||
text += f"\n{packages}"
|
text += f'\n{packages}'
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
@ -30,5 +30,5 @@ class XorgProfile(Profile):
|
||||||
@override
|
@override
|
||||||
def packages(self) -> list[str]:
|
def packages(self) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"xorg-server",
|
'xorg-server',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ class Arguments:
|
||||||
creds_decryption_key: str | None = None
|
creds_decryption_key: str | None = None
|
||||||
silent: bool = False
|
silent: bool = False
|
||||||
dry_run: bool = False
|
dry_run: bool = False
|
||||||
script: str = "guided"
|
script: str = 'guided'
|
||||||
mountpoint: Path = Path("/mnt")
|
mountpoint: Path = Path('/mnt')
|
||||||
skip_ntp: bool = False
|
skip_ntp: bool = False
|
||||||
skip_wkd: bool = False
|
skip_wkd: bool = False
|
||||||
debug: bool = False
|
debug: bool = False
|
||||||
|
|
@ -56,7 +56,7 @@ class Arguments:
|
||||||
class ArchConfig:
|
class ArchConfig:
|
||||||
version: str | None = None
|
version: str | None = None
|
||||||
locale_config: LocaleConfiguration | None = None
|
locale_config: LocaleConfiguration | None = None
|
||||||
archinstall_language: Language = field(default_factory=lambda: translation_handler.get_language_by_abbr("en"))
|
archinstall_language: Language = field(default_factory=lambda: translation_handler.get_language_by_abbr('en'))
|
||||||
disk_config: DiskLayoutConfiguration | None = None
|
disk_config: DiskLayoutConfiguration | None = None
|
||||||
profile_config: ProfileConfiguration | None = None
|
profile_config: ProfileConfiguration | None = None
|
||||||
mirror_config: MirrorConfiguration | None = None
|
mirror_config: MirrorConfiguration | None = None
|
||||||
|
|
@ -64,13 +64,13 @@ class ArchConfig:
|
||||||
bootloader: Bootloader = field(default=Bootloader.get_default())
|
bootloader: Bootloader = field(default=Bootloader.get_default())
|
||||||
uki: bool = False
|
uki: bool = False
|
||||||
audio_config: AudioConfiguration | None = None
|
audio_config: AudioConfiguration | None = None
|
||||||
hostname: str = "archlinux"
|
hostname: str = 'archlinux'
|
||||||
kernels: list[str] = field(default_factory=lambda: ["linux"])
|
kernels: list[str] = field(default_factory=lambda: ['linux'])
|
||||||
ntp: bool = True
|
ntp: bool = True
|
||||||
packages: list[str] = field(default_factory=list)
|
packages: list[str] = field(default_factory=list)
|
||||||
parallel_downloads: int = 0
|
parallel_downloads: int = 0
|
||||||
swap: bool = True
|
swap: bool = True
|
||||||
timezone: str = "UTC"
|
timezone: str = 'UTC'
|
||||||
services: list[str] = field(default_factory=list)
|
services: list[str] = field(default_factory=list)
|
||||||
custom_commands: list[str] = field(default_factory=list)
|
custom_commands: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
@ -81,70 +81,70 @@ class ArchConfig:
|
||||||
|
|
||||||
def unsafe_json(self) -> dict[str, Any]:
|
def unsafe_json(self) -> dict[str, Any]:
|
||||||
config = {
|
config = {
|
||||||
"users": [user.json() for user in self.users],
|
'users': [user.json() for user in self.users],
|
||||||
"root_enc_password": self.root_enc_password.enc_password if self.root_enc_password else None,
|
'root_enc_password': self.root_enc_password.enc_password if self.root_enc_password else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.disk_encryption and self.disk_encryption.encryption_password:
|
if self.disk_encryption and self.disk_encryption.encryption_password:
|
||||||
config["encryption_password"] = self.disk_encryption.encryption_password.plaintext
|
config['encryption_password'] = self.disk_encryption.encryption_password.plaintext
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def safe_json(self) -> dict[str, Any]:
|
def safe_json(self) -> dict[str, Any]:
|
||||||
config: Any = {
|
config: Any = {
|
||||||
"version": self.version,
|
'version': self.version,
|
||||||
"archinstall-language": self.archinstall_language.json(),
|
'archinstall-language': self.archinstall_language.json(),
|
||||||
"hostname": self.hostname,
|
'hostname': self.hostname,
|
||||||
"kernels": self.kernels,
|
'kernels': self.kernels,
|
||||||
"ntp": self.ntp,
|
'ntp': self.ntp,
|
||||||
"packages": self.packages,
|
'packages': self.packages,
|
||||||
"parallel_downloads": self.parallel_downloads,
|
'parallel_downloads': self.parallel_downloads,
|
||||||
"swap": self.swap,
|
'swap': self.swap,
|
||||||
"timezone": self.timezone,
|
'timezone': self.timezone,
|
||||||
"services": self.services,
|
'services': self.services,
|
||||||
"custom_commands": self.custom_commands,
|
'custom_commands': self.custom_commands,
|
||||||
"bootloader": self.bootloader.json(),
|
'bootloader': self.bootloader.json(),
|
||||||
"audio_config": self.audio_config.json() if self.audio_config else None,
|
'audio_config': self.audio_config.json() if self.audio_config else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.locale_config:
|
if self.locale_config:
|
||||||
config["locale_config"] = self.locale_config.json()
|
config['locale_config'] = self.locale_config.json()
|
||||||
|
|
||||||
if self.disk_config:
|
if self.disk_config:
|
||||||
config["disk_config"] = self.disk_config.json()
|
config['disk_config'] = self.disk_config.json()
|
||||||
|
|
||||||
if self.disk_encryption:
|
if self.disk_encryption:
|
||||||
config["disk_encryption"] = self.disk_encryption.json()
|
config['disk_encryption'] = self.disk_encryption.json()
|
||||||
|
|
||||||
if self.profile_config:
|
if self.profile_config:
|
||||||
config["profile_config"] = self.profile_config.json()
|
config['profile_config'] = self.profile_config.json()
|
||||||
|
|
||||||
if self.mirror_config:
|
if self.mirror_config:
|
||||||
config["mirror_config"] = self.mirror_config.json()
|
config['mirror_config'] = self.mirror_config.json()
|
||||||
|
|
||||||
if self.network_config:
|
if self.network_config:
|
||||||
config["network_config"] = self.network_config.json()
|
config['network_config'] = self.network_config.json()
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_config(cls, args_config: dict[str, Any]) -> "ArchConfig":
|
def from_config(cls, args_config: dict[str, Any]) -> 'ArchConfig':
|
||||||
arch_config = ArchConfig()
|
arch_config = ArchConfig()
|
||||||
|
|
||||||
arch_config.locale_config = LocaleConfiguration.parse_arg(args_config)
|
arch_config.locale_config = LocaleConfiguration.parse_arg(args_config)
|
||||||
|
|
||||||
if archinstall_lang := args_config.get("archinstall-language", None):
|
if archinstall_lang := args_config.get('archinstall-language', None):
|
||||||
arch_config.archinstall_language = translation_handler.get_language_by_name(archinstall_lang)
|
arch_config.archinstall_language = translation_handler.get_language_by_name(archinstall_lang)
|
||||||
|
|
||||||
if disk_config := args_config.get("disk_config", {}):
|
if disk_config := args_config.get('disk_config', {}):
|
||||||
arch_config.disk_config = DiskLayoutConfiguration.parse_arg(disk_config)
|
arch_config.disk_config = DiskLayoutConfiguration.parse_arg(disk_config)
|
||||||
|
|
||||||
if profile_config := args_config.get("profile_config", None):
|
if profile_config := args_config.get('profile_config', None):
|
||||||
arch_config.profile_config = ProfileConfiguration.parse_arg(profile_config)
|
arch_config.profile_config = ProfileConfiguration.parse_arg(profile_config)
|
||||||
|
|
||||||
if mirror_config := args_config.get("mirror_config", None):
|
if mirror_config := args_config.get('mirror_config', None):
|
||||||
backwards_compatible_repo = []
|
backwards_compatible_repo = []
|
||||||
if additional_repositories := args_config.get("additional-repositories", []):
|
if additional_repositories := args_config.get('additional-repositories', []):
|
||||||
backwards_compatible_repo = [Repository(r) for r in additional_repositories]
|
backwards_compatible_repo = [Repository(r) for r in additional_repositories]
|
||||||
|
|
||||||
arch_config.mirror_config = MirrorConfiguration.parse_args(
|
arch_config.mirror_config = MirrorConfiguration.parse_args(
|
||||||
|
|
@ -152,62 +152,62 @@ class ArchConfig:
|
||||||
backwards_compatible_repo,
|
backwards_compatible_repo,
|
||||||
)
|
)
|
||||||
|
|
||||||
if net_config := args_config.get("network_config", None):
|
if net_config := args_config.get('network_config', None):
|
||||||
arch_config.network_config = NetworkConfiguration.parse_arg(net_config)
|
arch_config.network_config = NetworkConfiguration.parse_arg(net_config)
|
||||||
|
|
||||||
# DEPRECATED: backwards copatibility
|
# DEPRECATED: backwards copatibility
|
||||||
if users := args_config.get("!users", None):
|
if users := args_config.get('!users', None):
|
||||||
arch_config.users = User.parse_arguments(users)
|
arch_config.users = User.parse_arguments(users)
|
||||||
|
|
||||||
if users := args_config.get("users", None):
|
if users := args_config.get('users', None):
|
||||||
arch_config.users = User.parse_arguments(users)
|
arch_config.users = User.parse_arguments(users)
|
||||||
|
|
||||||
if bootloader_config := args_config.get("bootloader", None):
|
if bootloader_config := args_config.get('bootloader', None):
|
||||||
arch_config.bootloader = Bootloader.from_arg(bootloader_config)
|
arch_config.bootloader = Bootloader.from_arg(bootloader_config)
|
||||||
|
|
||||||
if args_config.get("uki") and not arch_config.bootloader.has_uki_support():
|
if args_config.get('uki') and not arch_config.bootloader.has_uki_support():
|
||||||
arch_config.uki = False
|
arch_config.uki = False
|
||||||
|
|
||||||
if audio_config := args_config.get("audio_config", None):
|
if audio_config := args_config.get('audio_config', None):
|
||||||
arch_config.audio_config = AudioConfiguration.parse_arg(audio_config)
|
arch_config.audio_config = AudioConfiguration.parse_arg(audio_config)
|
||||||
|
|
||||||
if args_config.get("disk_encryption", None) is not None and arch_config.disk_config is not None:
|
if args_config.get('disk_encryption', None) is not None and arch_config.disk_config is not None:
|
||||||
arch_config.disk_encryption = DiskEncryption.parse_arg(
|
arch_config.disk_encryption = DiskEncryption.parse_arg(
|
||||||
arch_config.disk_config,
|
arch_config.disk_config,
|
||||||
args_config["disk_encryption"],
|
args_config['disk_encryption'],
|
||||||
Password(plaintext=args_config.get("encryption_password", "")),
|
Password(plaintext=args_config.get('encryption_password', '')),
|
||||||
)
|
)
|
||||||
|
|
||||||
if hostname := args_config.get("hostname", ""):
|
if hostname := args_config.get('hostname', ''):
|
||||||
arch_config.hostname = hostname
|
arch_config.hostname = hostname
|
||||||
|
|
||||||
if kernels := args_config.get("kernels", []):
|
if kernels := args_config.get('kernels', []):
|
||||||
arch_config.kernels = kernels
|
arch_config.kernels = kernels
|
||||||
|
|
||||||
arch_config.ntp = args_config.get("ntp", True)
|
arch_config.ntp = args_config.get('ntp', True)
|
||||||
|
|
||||||
if packages := args_config.get("packages", []):
|
if packages := args_config.get('packages', []):
|
||||||
arch_config.packages = packages
|
arch_config.packages = packages
|
||||||
|
|
||||||
if parallel_downloads := args_config.get("parallel_downloads", 0):
|
if parallel_downloads := args_config.get('parallel_downloads', 0):
|
||||||
arch_config.parallel_downloads = parallel_downloads
|
arch_config.parallel_downloads = parallel_downloads
|
||||||
|
|
||||||
arch_config.swap = args_config.get("swap", True)
|
arch_config.swap = args_config.get('swap', True)
|
||||||
|
|
||||||
if timezone := args_config.get("timezone", "UTC"):
|
if timezone := args_config.get('timezone', 'UTC'):
|
||||||
arch_config.timezone = timezone
|
arch_config.timezone = timezone
|
||||||
|
|
||||||
if services := args_config.get("services", []):
|
if services := args_config.get('services', []):
|
||||||
arch_config.services = services
|
arch_config.services = services
|
||||||
|
|
||||||
# DEPRECATED: backwards compatibility
|
# DEPRECATED: backwards compatibility
|
||||||
if root_password := args_config.get("!root-password", None):
|
if root_password := args_config.get('!root-password', None):
|
||||||
arch_config.root_enc_password = Password(plaintext=root_password)
|
arch_config.root_enc_password = Password(plaintext=root_password)
|
||||||
|
|
||||||
if enc_password := args_config.get("root_enc_password", None):
|
if enc_password := args_config.get('root_enc_password', None):
|
||||||
arch_config.root_enc_password = Password(enc_password=enc_password)
|
arch_config.root_enc_password = Password(enc_password=enc_password)
|
||||||
|
|
||||||
if custom_commands := args_config.get("custom_commands", []):
|
if custom_commands := args_config.get('custom_commands', []):
|
||||||
arch_config.custom_commands = custom_commands
|
arch_config.custom_commands = custom_commands
|
||||||
|
|
||||||
return arch_config
|
return arch_config
|
||||||
|
|
@ -239,135 +239,135 @@ class ArchConfigHandler:
|
||||||
|
|
||||||
def _get_version(self) -> str:
|
def _get_version(self) -> str:
|
||||||
try:
|
try:
|
||||||
return version("archinstall")
|
return version('archinstall')
|
||||||
except Exception:
|
except Exception:
|
||||||
return "Archinstall version not found"
|
return 'Archinstall version not found'
|
||||||
|
|
||||||
def _define_arguments(self) -> ArgumentParser:
|
def _define_arguments(self) -> ArgumentParser:
|
||||||
parser = ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
parser = ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-v",
|
'-v',
|
||||||
"--version",
|
'--version',
|
||||||
action="version",
|
action='version',
|
||||||
default=False,
|
default=False,
|
||||||
version="%(prog)s " + self._get_version(),
|
version='%(prog)s ' + self._get_version(),
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config",
|
'--config',
|
||||||
type=Path,
|
type=Path,
|
||||||
nargs="?",
|
nargs='?',
|
||||||
default=None,
|
default=None,
|
||||||
help="JSON configuration file",
|
help='JSON configuration file',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config-url",
|
'--config-url',
|
||||||
type=str,
|
type=str,
|
||||||
nargs="?",
|
nargs='?',
|
||||||
default=None,
|
default=None,
|
||||||
help="Url to a JSON configuration file",
|
help='Url to a JSON configuration file',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--creds",
|
'--creds',
|
||||||
type=Path,
|
type=Path,
|
||||||
nargs="?",
|
nargs='?',
|
||||||
default=None,
|
default=None,
|
||||||
help="JSON credentials configuration file",
|
help='JSON credentials configuration file',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--creds-url",
|
'--creds-url',
|
||||||
type=str,
|
type=str,
|
||||||
nargs="?",
|
nargs='?',
|
||||||
default=None,
|
default=None,
|
||||||
help="Url to a JSON credentials configuration file",
|
help='Url to a JSON credentials configuration file',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--creds-decryption-key",
|
'--creds-decryption-key',
|
||||||
type=str,
|
type=str,
|
||||||
nargs="?",
|
nargs='?',
|
||||||
default=None,
|
default=None,
|
||||||
help="Decryption key for credentials file",
|
help='Decryption key for credentials file',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--silent",
|
'--silent',
|
||||||
action="store_true",
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help="WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored",
|
help='WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--dry-run",
|
'--dry-run',
|
||||||
"--dry_run",
|
'--dry_run',
|
||||||
action="store_true",
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help="Generates a configuration file and then exits instead of performing an installation",
|
help='Generates a configuration file and then exits instead of performing an installation',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--script",
|
'--script',
|
||||||
default="guided",
|
default='guided',
|
||||||
nargs="?",
|
nargs='?',
|
||||||
help="Script to run for installation",
|
help='Script to run for installation',
|
||||||
type=str,
|
type=str,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--mountpoint",
|
'--mountpoint',
|
||||||
type=Path,
|
type=Path,
|
||||||
nargs="?",
|
nargs='?',
|
||||||
default=Path("/mnt"),
|
default=Path('/mnt'),
|
||||||
help="Define an alternate mount point for installation",
|
help='Define an alternate mount point for installation',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--skip-ntp",
|
'--skip-ntp',
|
||||||
action="store_true",
|
action='store_true',
|
||||||
help="Disables NTP checks during installation",
|
help='Disables NTP checks during installation',
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--skip-wkd",
|
'--skip-wkd',
|
||||||
action="store_true",
|
action='store_true',
|
||||||
help="Disables checking if archlinux keyring wkd sync is complete.",
|
help='Disables checking if archlinux keyring wkd sync is complete.',
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--debug",
|
'--debug',
|
||||||
action="store_true",
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help="Adds debug info into the log",
|
help='Adds debug info into the log',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--offline",
|
'--offline',
|
||||||
action="store_true",
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help="Disabled online upstream services such as package search and key-ring auto update.",
|
help='Disabled online upstream services such as package search and key-ring auto update.',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--no-pkg-lookups",
|
'--no-pkg-lookups',
|
||||||
action="store_true",
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help="Disabled package validation specifically prior to starting installation.",
|
help='Disabled package validation specifically prior to starting installation.',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--plugin",
|
'--plugin',
|
||||||
nargs="?",
|
nargs='?',
|
||||||
type=str,
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
help="File path to a plugin to load",
|
help='File path to a plugin to load',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--skip-version-check",
|
'--skip-version-check',
|
||||||
action="store_true",
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help="Skip the version check when running archinstall",
|
help='Skip the version check when running archinstall',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--advanced",
|
'--advanced',
|
||||||
action="store_true",
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help="Enabled advanced options",
|
help='Enabled advanced options',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--verbose",
|
'--verbose',
|
||||||
action="store_true",
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help="Enabled verbose options",
|
help='Enabled verbose options',
|
||||||
)
|
)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
@ -382,15 +382,15 @@ class ArchConfigHandler:
|
||||||
args.silent = False
|
args.silent = False
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
warn(f"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!")
|
warn(f'Warning: --debug mode will write certain credentials to {storage["LOG_PATH"]}/{storage["LOG_FILE"]}!')
|
||||||
|
|
||||||
if args.plugin:
|
if args.plugin:
|
||||||
plugin_path = Path(args.plugin)
|
plugin_path = Path(args.plugin)
|
||||||
load_plugin(plugin_path)
|
load_plugin(plugin_path)
|
||||||
|
|
||||||
if args.creds_decryption_key is None:
|
if args.creds_decryption_key is None:
|
||||||
if os.environ.get("ARCHINSTALL_CREDS_DECRYPTION_KEY"):
|
if os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY'):
|
||||||
args.creds_decryption_key = os.environ.get("ARCHINSTALL_CREDS_DECRYPTION_KEY")
|
args.creds_decryption_key = os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY')
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
@ -422,27 +422,27 @@ class ArchConfigHandler:
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def _process_creds_data(self, creds_data: str) -> dict[str, Any] | None:
|
def _process_creds_data(self, creds_data: str) -> dict[str, Any] | None:
|
||||||
if creds_data.startswith("$"): # encrypted data
|
if creds_data.startswith('$'): # encrypted data
|
||||||
if self._args.creds_decryption_key is not None:
|
if self._args.creds_decryption_key is not None:
|
||||||
try:
|
try:
|
||||||
creds_data = decrypt(creds_data, self._args.creds_decryption_key)
|
creds_data = decrypt(creds_data, self._args.creds_decryption_key)
|
||||||
return json.loads(creds_data)
|
return json.loads(creds_data)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
if "Invalid password" in str(err):
|
if 'Invalid password' in str(err):
|
||||||
error(tr("Incorrect credentials file decryption password"))
|
error(tr('Incorrect credentials file decryption password'))
|
||||||
exit(1)
|
exit(1)
|
||||||
else:
|
else:
|
||||||
debug(f"Error decrypting credentials file: {err}")
|
debug(f'Error decrypting credentials file: {err}')
|
||||||
raise err from err
|
raise err from err
|
||||||
else:
|
else:
|
||||||
incorrect_password = False
|
incorrect_password = False
|
||||||
|
|
||||||
with Tui():
|
with Tui():
|
||||||
while True:
|
while True:
|
||||||
header = tr("Incorrect password") if incorrect_password else None
|
header = tr('Incorrect password') if incorrect_password else None
|
||||||
|
|
||||||
decryption_pwd = get_password(
|
decryption_pwd = get_password(
|
||||||
text=tr("Credentials file decryption password"),
|
text=tr('Credentials file decryption password'),
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=False,
|
allow_skip=False,
|
||||||
skip_confirmation=True,
|
skip_confirmation=True,
|
||||||
|
|
@ -455,11 +455,11 @@ class ArchConfigHandler:
|
||||||
creds_data = decrypt(creds_data, decryption_pwd.plaintext)
|
creds_data = decrypt(creds_data, decryption_pwd.plaintext)
|
||||||
break
|
break
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
if "Invalid password" in str(err):
|
if 'Invalid password' in str(err):
|
||||||
debug("Incorrect credentials file decryption password")
|
debug('Incorrect credentials file decryption password')
|
||||||
incorrect_password = True
|
incorrect_password = True
|
||||||
else:
|
else:
|
||||||
debug(f"Error decrypting credentials file: {err}")
|
debug(f'Error decrypting credentials file: {err}')
|
||||||
raise err from err
|
raise err from err
|
||||||
|
|
||||||
return json.loads(creds_data)
|
return json.loads(creds_data)
|
||||||
|
|
@ -467,19 +467,19 @@ class ArchConfigHandler:
|
||||||
def _fetch_from_url(self, url: str) -> str:
|
def _fetch_from_url(self, url: str) -> str:
|
||||||
if urllib.parse.urlparse(url).scheme:
|
if urllib.parse.urlparse(url).scheme:
|
||||||
try:
|
try:
|
||||||
req = Request(url, headers={"User-Agent": "ArchInstall"})
|
req = Request(url, headers={'User-Agent': 'ArchInstall'})
|
||||||
with urlopen(req) as resp:
|
with urlopen(req) as resp:
|
||||||
return resp.read().decode("utf-8")
|
return resp.read().decode('utf-8')
|
||||||
except urllib.error.HTTPError as err:
|
except urllib.error.HTTPError as err:
|
||||||
error(f"Could not fetch JSON from {url}: {err}")
|
error(f'Could not fetch JSON from {url}: {err}')
|
||||||
else:
|
else:
|
||||||
error("Not a valid url")
|
error('Not a valid url')
|
||||||
|
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
def _read_file(self, path: Path) -> str:
|
def _read_file(self, path: Path) -> str:
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
error(f"Could not find file {path}")
|
error(f'Could not find file {path}')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
return path.read_text()
|
return path.read_text()
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,13 @@ from .storage import storage
|
||||||
class Boot:
|
class Boot:
|
||||||
def __init__(self, installation: Installer):
|
def __init__(self, installation: Installer):
|
||||||
self.instance = installation
|
self.instance = installation
|
||||||
self.container_name = "archinstall"
|
self.container_name = 'archinstall'
|
||||||
self.session: SysCommandWorker | None = None
|
self.session: SysCommandWorker | None = None
|
||||||
self.ready = False
|
self.ready = False
|
||||||
|
|
||||||
def __enter__(self) -> "Boot":
|
def __enter__(self) -> 'Boot':
|
||||||
if (existing_session := storage.get("active_boot", None)) and existing_session.instance != self.instance:
|
if (existing_session := storage.get('active_boot', None)) and existing_session.instance != self.instance:
|
||||||
raise KeyError("Archinstall only supports booting up one instance and another session is already active.")
|
raise KeyError('Archinstall only supports booting up one instance and another session is already active.')
|
||||||
|
|
||||||
if existing_session:
|
if existing_session:
|
||||||
self.session = existing_session.session
|
self.session = existing_session.session
|
||||||
|
|
@ -27,24 +27,24 @@ class Boot:
|
||||||
# of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual.
|
# of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual.
|
||||||
self.session = SysCommandWorker(
|
self.session = SysCommandWorker(
|
||||||
[
|
[
|
||||||
"systemd-nspawn",
|
'systemd-nspawn',
|
||||||
"-D",
|
'-D',
|
||||||
str(self.instance.target),
|
str(self.instance.target),
|
||||||
"--timezone=off",
|
'--timezone=off',
|
||||||
"-b",
|
'-b',
|
||||||
"--no-pager",
|
'--no-pager',
|
||||||
"--machine",
|
'--machine',
|
||||||
self.container_name,
|
self.container_name,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.ready and self.session:
|
if not self.ready and self.session:
|
||||||
while self.session.is_alive():
|
while self.session.is_alive():
|
||||||
if b" login:" in self.session:
|
if b' login:' in self.session:
|
||||||
self.ready = True
|
self.ready = True
|
||||||
break
|
break
|
||||||
|
|
||||||
storage["active_boot"] = self
|
storage['active_boot'] = self
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *args: str, **kwargs: str) -> None:
|
def __exit__(self, *args: str, **kwargs: str) -> None:
|
||||||
|
|
@ -54,14 +54,14 @@ class Boot:
|
||||||
if len(args) >= 2 and args[1]:
|
if len(args) >= 2 and args[1]:
|
||||||
error(
|
error(
|
||||||
args[1],
|
args[1],
|
||||||
f"The error above occurred in a temporary boot-up of the installation {self.instance}",
|
f'The error above occurred in a temporary boot-up of the installation {self.instance}',
|
||||||
)
|
)
|
||||||
|
|
||||||
shutdown = None
|
shutdown = None
|
||||||
shutdown_exit_code: int | None = -1
|
shutdown_exit_code: int | None = -1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
shutdown = SysCommand(f"systemd-run --machine={self.container_name} --pty shutdown now")
|
shutdown = SysCommand(f'systemd-run --machine={self.container_name} --pty shutdown now')
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
shutdown_exit_code = err.exit_code
|
shutdown_exit_code = err.exit_code
|
||||||
|
|
||||||
|
|
@ -73,12 +73,12 @@ class Boot:
|
||||||
shutdown_exit_code = shutdown.exit_code
|
shutdown_exit_code = shutdown.exit_code
|
||||||
|
|
||||||
if self.session and (self.session.exit_code == 0 or shutdown_exit_code == 0):
|
if self.session and (self.session.exit_code == 0 or shutdown_exit_code == 0):
|
||||||
storage["active_boot"] = None
|
storage['active_boot'] = None
|
||||||
else:
|
else:
|
||||||
session_exit_code = self.session.exit_code if self.session else -1
|
session_exit_code = self.session.exit_code if self.session else -1
|
||||||
|
|
||||||
raise SysCallError(
|
raise SysCallError(
|
||||||
f"Could not shut down temporary boot of {self.instance}: {session_exit_code}/{shutdown_exit_code}",
|
f'Could not shut down temporary boot of {self.instance}: {session_exit_code}/{shutdown_exit_code}',
|
||||||
exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code])),
|
exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code])),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -99,17 +99,17 @@ class Boot:
|
||||||
return self.session.is_alive()
|
return self.session.is_alive()
|
||||||
|
|
||||||
def SysCommand(self, cmd: list[str], *args, **kwargs) -> SysCommand:
|
def SysCommand(self, cmd: list[str], *args, **kwargs) -> SysCommand:
|
||||||
if cmd[0][0] != "/" and cmd[0][:2] != "./":
|
if cmd[0][0] != '/' and cmd[0][:2] != './':
|
||||||
# This check is also done in SysCommand & SysCommandWorker.
|
# This check is also done in SysCommand & SysCommandWorker.
|
||||||
# However, that check is done for `machinectl` and not for our chroot command.
|
# However, that check is done for `machinectl` and not for our chroot command.
|
||||||
# So this wrapper for SysCommand will do this additionally.
|
# So this wrapper for SysCommand will do this additionally.
|
||||||
|
|
||||||
cmd[0] = locate_binary(cmd[0])
|
cmd[0] = locate_binary(cmd[0])
|
||||||
|
|
||||||
return SysCommand(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs)
|
return SysCommand(['systemd-run', f'--machine={self.container_name}', '--pty', *cmd], *args, **kwargs)
|
||||||
|
|
||||||
def SysCommandWorker(self, cmd: list[str], *args, **kwargs) -> SysCommandWorker:
|
def SysCommandWorker(self, cmd: list[str], *args, **kwargs) -> SysCommandWorker:
|
||||||
if cmd[0][0] != "/" and cmd[0][:2] != "./":
|
if cmd[0][0] != '/' and cmd[0][:2] != './':
|
||||||
cmd[0] = locate_binary(cmd[0])
|
cmd[0] = locate_binary(cmd[0])
|
||||||
|
|
||||||
return SysCommandWorker(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs)
|
return SysCommandWorker(['systemd-run', f'--machine={self.container_name}', '--pty', *cmd], *args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@ class ConfigurationOutput:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._config = config
|
self._config = config
|
||||||
self._default_save_path = storage.get("LOG_PATH", Path("."))
|
self._default_save_path = storage.get('LOG_PATH', Path('.'))
|
||||||
self._user_config_file = Path("user_configuration.json")
|
self._user_config_file = Path('user_configuration.json')
|
||||||
self._user_creds_file = Path("user_credentials.json")
|
self._user_creds_file = Path('user_credentials.json')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_configuration_file(self) -> Path:
|
def user_configuration_file(self) -> Path:
|
||||||
|
|
@ -50,12 +50,12 @@ class ConfigurationOutput:
|
||||||
return json.dumps(out, indent=4, sort_keys=True, cls=UNSAFE_JSON)
|
return json.dumps(out, indent=4, sort_keys=True, cls=UNSAFE_JSON)
|
||||||
|
|
||||||
def write_debug(self) -> None:
|
def write_debug(self) -> None:
|
||||||
debug(" -- Chosen configuration --")
|
debug(' -- Chosen configuration --')
|
||||||
debug(self.user_config_to_json())
|
debug(self.user_config_to_json())
|
||||||
|
|
||||||
def confirm_config(self) -> bool:
|
def confirm_config(self) -> bool:
|
||||||
header = f"{tr('The specified configuration will be applied')}. "
|
header = f'{tr("The specified configuration will be applied")}. '
|
||||||
header += tr("Would you like to continue?") + "\n"
|
header += tr('Would you like to continue?') + '\n'
|
||||||
|
|
||||||
with Tui():
|
with Tui():
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
|
|
@ -69,9 +69,9 @@ class ConfigurationOutput:
|
||||||
columns=2,
|
columns=2,
|
||||||
orientation=Orientation.HORIZONTAL,
|
orientation=Orientation.HORIZONTAL,
|
||||||
allow_skip=False,
|
allow_skip=False,
|
||||||
preview_size="auto",
|
preview_size='auto',
|
||||||
preview_style=PreviewStyle.BOTTOM,
|
preview_style=PreviewStyle.BOTTOM,
|
||||||
preview_frame=FrameProperties.max(tr("Configuration")),
|
preview_frame=FrameProperties.max(tr('Configuration')),
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
if result.item() != MenuItem.yes():
|
if result.item() != MenuItem.yes():
|
||||||
|
|
@ -83,8 +83,8 @@ class ConfigurationOutput:
|
||||||
dest_path_ok = dest_path.exists() and dest_path.is_dir()
|
dest_path_ok = dest_path.exists() and dest_path.is_dir()
|
||||||
if not dest_path_ok:
|
if not dest_path_ok:
|
||||||
warn(
|
warn(
|
||||||
f"Destination directory {dest_path.resolve()} does not exist or is not a directory\n.",
|
f'Destination directory {dest_path.resolve()} does not exist or is not a directory\n.',
|
||||||
"Configuration files can not be saved",
|
'Configuration files can not be saved',
|
||||||
)
|
)
|
||||||
return dest_path_ok
|
return dest_path_ok
|
||||||
|
|
||||||
|
|
@ -126,36 +126,36 @@ class ConfigurationOutput:
|
||||||
def save_config(config: ArchConfig) -> None:
|
def save_config(config: ArchConfig) -> None:
|
||||||
def preview(item: MenuItem) -> str | None:
|
def preview(item: MenuItem) -> str | None:
|
||||||
match item.value:
|
match item.value:
|
||||||
case "user_config":
|
case 'user_config':
|
||||||
serialized = config_output.user_config_to_json()
|
serialized = config_output.user_config_to_json()
|
||||||
return f"{config_output.user_configuration_file}\n{serialized}"
|
return f'{config_output.user_configuration_file}\n{serialized}'
|
||||||
case "user_creds":
|
case 'user_creds':
|
||||||
if maybe_serial := config_output.user_credentials_to_json():
|
if maybe_serial := config_output.user_credentials_to_json():
|
||||||
return f"{config_output.user_credentials_file}\n{maybe_serial}"
|
return f'{config_output.user_credentials_file}\n{maybe_serial}'
|
||||||
return tr("No configuration")
|
return tr('No configuration')
|
||||||
case "all":
|
case 'all':
|
||||||
output = [str(config_output.user_configuration_file)]
|
output = [str(config_output.user_configuration_file)]
|
||||||
config_output.user_credentials_to_json()
|
config_output.user_credentials_to_json()
|
||||||
output.append(str(config_output.user_credentials_file))
|
output.append(str(config_output.user_credentials_file))
|
||||||
return "\n".join(output)
|
return '\n'.join(output)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
config_output = ConfigurationOutput(config)
|
config_output = ConfigurationOutput(config)
|
||||||
|
|
||||||
items = [
|
items = [
|
||||||
MenuItem(
|
MenuItem(
|
||||||
tr("Save user configuration (including disk layout)"),
|
tr('Save user configuration (including disk layout)'),
|
||||||
value="user_config",
|
value='user_config',
|
||||||
preview_action=preview,
|
preview_action=preview,
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
tr("Save user credentials"),
|
tr('Save user credentials'),
|
||||||
value="user_creds",
|
value='user_creds',
|
||||||
preview_action=preview,
|
preview_action=preview,
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
tr("Save all"),
|
tr('Save all'),
|
||||||
value="all",
|
value='all',
|
||||||
preview_action=preview,
|
preview_action=preview,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
@ -164,8 +164,8 @@ def save_config(config: ArchConfig) -> None:
|
||||||
result = SelectMenu[str](
|
result = SelectMenu[str](
|
||||||
group,
|
group,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
preview_frame=FrameProperties.max(tr("Configuration")),
|
preview_frame=FrameProperties.max(tr('Configuration')),
|
||||||
preview_size="auto",
|
preview_size='auto',
|
||||||
preview_style=PreviewStyle.RIGHT,
|
preview_style=PreviewStyle.RIGHT,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -175,21 +175,21 @@ def save_config(config: ArchConfig) -> None:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
save_option = result.get_value()
|
save_option = result.get_value()
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
readline.set_completer_delims("\t\n=")
|
readline.set_completer_delims('\t\n=')
|
||||||
readline.parse_and_bind("tab: complete")
|
readline.parse_and_bind('tab: complete')
|
||||||
|
|
||||||
dest_path = prompt_dir(
|
dest_path = prompt_dir(
|
||||||
tr("Directory"),
|
tr('Directory'),
|
||||||
tr("Enter a directory for the configuration(s) to be saved (tab completion enabled)") + "\n",
|
tr('Enter a directory for the configuration(s) to be saved (tab completion enabled)') + '\n',
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not dest_path:
|
if not dest_path:
|
||||||
return
|
return
|
||||||
|
|
||||||
header = tr("Do you want to save the configuration file(s) to {}?").format(dest_path)
|
header = tr('Do you want to save the configuration file(s) to {}?').format(dest_path)
|
||||||
|
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
group.focus_item = MenuItem.yes()
|
group.focus_item = MenuItem.yes()
|
||||||
|
|
@ -208,9 +208,9 @@ def save_config(config: ArchConfig) -> None:
|
||||||
if result.item() == MenuItem.no():
|
if result.item() == MenuItem.no():
|
||||||
return
|
return
|
||||||
|
|
||||||
debug(f"Saving configuration files to {dest_path.absolute()}")
|
debug(f'Saving configuration files to {dest_path.absolute()}')
|
||||||
|
|
||||||
header = tr("Do you want to encrypt the user_credentials.json file?")
|
header = tr('Do you want to encrypt the user_credentials.json file?')
|
||||||
|
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
group.focus_item = MenuItem.no()
|
group.focus_item = MenuItem.no()
|
||||||
|
|
@ -229,7 +229,7 @@ def save_config(config: ArchConfig) -> None:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
if result.item() == MenuItem.yes():
|
if result.item() == MenuItem.yes():
|
||||||
password = get_password(
|
password = get_password(
|
||||||
text=tr("Credentials file encryption password"),
|
text=tr('Credentials file encryption password'),
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -237,9 +237,9 @@ def save_config(config: ArchConfig) -> None:
|
||||||
enc_password = password.plaintext
|
enc_password = password.plaintext
|
||||||
|
|
||||||
match save_option:
|
match save_option:
|
||||||
case "user_config":
|
case 'user_config':
|
||||||
config_output.save_user_config(dest_path)
|
config_output.save_user_config(dest_path)
|
||||||
case "user_creds":
|
case 'user_creds':
|
||||||
config_output.save_user_creds(dest_path, password=enc_password)
|
config_output.save_user_creds(dest_path, password=enc_password)
|
||||||
case "all":
|
case 'all':
|
||||||
config_output.save(dest_path, creds=True, password=enc_password)
|
config_output.save(dest_path, creds=True, password=enc_password)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
|
||||||
|
|
||||||
from .output import debug
|
from .output import debug
|
||||||
|
|
||||||
libcrypt = ctypes.CDLL("libcrypt.so")
|
libcrypt = ctypes.CDLL('libcrypt.so')
|
||||||
|
|
||||||
libcrypt.crypt.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
libcrypt.crypt.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
||||||
libcrypt.crypt.restype = ctypes.c_char_p
|
libcrypt.crypt.restype = ctypes.c_char_p
|
||||||
|
|
@ -16,19 +16,19 @@ libcrypt.crypt.restype = ctypes.c_char_p
|
||||||
libcrypt.crypt_gensalt.argtypes = [ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p, ctypes.c_int]
|
libcrypt.crypt_gensalt.argtypes = [ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p, ctypes.c_int]
|
||||||
libcrypt.crypt_gensalt.restype = ctypes.c_char_p
|
libcrypt.crypt_gensalt.restype = ctypes.c_char_p
|
||||||
|
|
||||||
LOGIN_DEFS = Path("/etc/login.defs")
|
LOGIN_DEFS = Path('/etc/login.defs')
|
||||||
|
|
||||||
|
|
||||||
def _search_login_defs(key: str) -> str | None:
|
def _search_login_defs(key: str) -> str | None:
|
||||||
defs = LOGIN_DEFS.read_text()
|
defs = LOGIN_DEFS.read_text()
|
||||||
for line in defs.split("\n"):
|
for line in defs.split('\n'):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
|
||||||
if line.startswith("#"):
|
if line.startswith('#'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if line.startswith(key):
|
if line.startswith(key):
|
||||||
value = line.split(" ")[1]
|
value = line.split(' ')[1]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
@ -36,12 +36,12 @@ def _search_login_defs(key: str) -> str | None:
|
||||||
|
|
||||||
def crypt_gen_salt(prefix: str | bytes, rounds: int) -> bytes:
|
def crypt_gen_salt(prefix: str | bytes, rounds: int) -> bytes:
|
||||||
if isinstance(prefix, str):
|
if isinstance(prefix, str):
|
||||||
prefix = prefix.encode("utf-8")
|
prefix = prefix.encode('utf-8')
|
||||||
|
|
||||||
setting = libcrypt.crypt_gensalt(prefix, rounds, None, 0)
|
setting = libcrypt.crypt_gensalt(prefix, rounds, None, 0)
|
||||||
|
|
||||||
if setting is None:
|
if setting is None:
|
||||||
raise ValueError(f"crypt_gensalt() returned NULL for prefix {prefix!r} and rounds {rounds}")
|
raise ValueError(f'crypt_gensalt() returned NULL for prefix {prefix!r} and rounds {rounds}')
|
||||||
|
|
||||||
return setting
|
return setting
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ def crypt_yescrypt(plaintext: str) -> str:
|
||||||
shows that the hashing rounds are determined from YESCRYPT_COST_FACTOR in /etc/login.defs
|
shows that the hashing rounds are determined from YESCRYPT_COST_FACTOR in /etc/login.defs
|
||||||
If no value was specified (or commented out) a default of 5 is choosen
|
If no value was specified (or commented out) a default of 5 is choosen
|
||||||
"""
|
"""
|
||||||
value = _search_login_defs("YESCRYPT_COST_FACTOR")
|
value = _search_login_defs('YESCRYPT_COST_FACTOR')
|
||||||
if value is not None:
|
if value is not None:
|
||||||
rounds = int(value)
|
rounds = int(value)
|
||||||
if rounds < 3:
|
if rounds < 3:
|
||||||
|
|
@ -63,17 +63,17 @@ def crypt_yescrypt(plaintext: str) -> str:
|
||||||
else:
|
else:
|
||||||
rounds = 5
|
rounds = 5
|
||||||
|
|
||||||
debug(f"Creating yescrypt hash with rounds {rounds}")
|
debug(f'Creating yescrypt hash with rounds {rounds}')
|
||||||
|
|
||||||
enc_plaintext = plaintext.encode("utf-8")
|
enc_plaintext = plaintext.encode('utf-8')
|
||||||
salt = crypt_gen_salt("$y$", rounds)
|
salt = crypt_gen_salt('$y$', rounds)
|
||||||
|
|
||||||
crypt_hash = libcrypt.crypt(enc_plaintext, salt)
|
crypt_hash = libcrypt.crypt(enc_plaintext, salt)
|
||||||
|
|
||||||
if crypt_hash is None:
|
if crypt_hash is None:
|
||||||
raise ValueError("crypt() returned NULL")
|
raise ValueError('crypt() returned NULL')
|
||||||
|
|
||||||
return crypt_hash.decode("utf-8")
|
return crypt_hash.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def _get_fernet(salt: bytes, password: str) -> Fernet:
|
def _get_fernet(salt: bytes, password: str) -> Fernet:
|
||||||
|
|
@ -90,7 +90,7 @@ def _get_fernet(salt: bytes, password: str) -> Fernet:
|
||||||
|
|
||||||
key = base64.urlsafe_b64encode(
|
key = base64.urlsafe_b64encode(
|
||||||
kdf.derive(
|
kdf.derive(
|
||||||
password.encode("utf-8"),
|
password.encode('utf-8'),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -100,26 +100,26 @@ def _get_fernet(salt: bytes, password: str) -> Fernet:
|
||||||
def encrypt(password: str, data: str) -> str:
|
def encrypt(password: str, data: str) -> str:
|
||||||
salt = os.urandom(16)
|
salt = os.urandom(16)
|
||||||
f = _get_fernet(salt, password)
|
f = _get_fernet(salt, password)
|
||||||
token = f.encrypt(data.encode("utf-8"))
|
token = f.encrypt(data.encode('utf-8'))
|
||||||
|
|
||||||
encoded_token = base64.urlsafe_b64encode(token).decode("utf-8")
|
encoded_token = base64.urlsafe_b64encode(token).decode('utf-8')
|
||||||
encoded_salt = base64.urlsafe_b64encode(salt).decode("utf-8")
|
encoded_salt = base64.urlsafe_b64encode(salt).decode('utf-8')
|
||||||
|
|
||||||
return f"$argon2id${encoded_salt}${encoded_token}"
|
return f'$argon2id${encoded_salt}${encoded_token}'
|
||||||
|
|
||||||
|
|
||||||
def decrypt(data: str, password: str) -> str:
|
def decrypt(data: str, password: str) -> str:
|
||||||
_, algo, encoded_salt, encoded_token = data.split("$")
|
_, algo, encoded_salt, encoded_token = data.split('$')
|
||||||
salt = base64.urlsafe_b64decode(encoded_salt)
|
salt = base64.urlsafe_b64decode(encoded_salt)
|
||||||
token = base64.urlsafe_b64decode(encoded_token)
|
token = base64.urlsafe_b64decode(encoded_token)
|
||||||
|
|
||||||
if algo != "argon2id":
|
if algo != 'argon2id':
|
||||||
raise ValueError(f"Unsupported algorithm {algo!r}")
|
raise ValueError(f'Unsupported algorithm {algo!r}')
|
||||||
|
|
||||||
f = _get_fernet(salt, password)
|
f = _get_fernet(salt, password)
|
||||||
try:
|
try:
|
||||||
decrypted = f.decrypt(token)
|
decrypted = f.decrypt(token)
|
||||||
except InvalidToken:
|
except InvalidToken:
|
||||||
raise ValueError("Invalid password")
|
raise ValueError('Invalid password')
|
||||||
|
|
||||||
return decrypted.decode("utf-8")
|
return decrypted.decode('utf-8')
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ from .utils import (
|
||||||
|
|
||||||
|
|
||||||
class DeviceHandler:
|
class DeviceHandler:
|
||||||
_TMP_BTRFS_MOUNT = Path("/mnt/arch_btrfs")
|
_TMP_BTRFS_MOUNT = Path('/mnt/arch_btrfs')
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._devices: dict[Path, BDevice] = {}
|
self._devices: dict[Path, BDevice] = {}
|
||||||
|
|
@ -73,16 +73,16 @@ class DeviceHandler:
|
||||||
devices = getAllDevices()
|
devices = getAllDevices()
|
||||||
devices.extend(self.get_loop_devices())
|
devices.extend(self.get_loop_devices())
|
||||||
|
|
||||||
archiso_mountpoint = Path("/run/archiso/airootfs")
|
archiso_mountpoint = Path('/run/archiso/airootfs')
|
||||||
|
|
||||||
for device in devices:
|
for device in devices:
|
||||||
dev_lsblk_info = find_lsblk_info(device.path, all_lsblk_info)
|
dev_lsblk_info = find_lsblk_info(device.path, all_lsblk_info)
|
||||||
|
|
||||||
if not dev_lsblk_info:
|
if not dev_lsblk_info:
|
||||||
debug(f"Device lsblk info not found: {device.path}")
|
debug(f'Device lsblk info not found: {device.path}')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if dev_lsblk_info.type == "rom":
|
if dev_lsblk_info.type == 'rom':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# exclude archiso loop device
|
# exclude archiso loop device
|
||||||
|
|
@ -95,7 +95,7 @@ class DeviceHandler:
|
||||||
else:
|
else:
|
||||||
disk = freshDisk(device, self.partition_table.value)
|
disk = freshDisk(device, self.partition_table.value)
|
||||||
except DiskException as err:
|
except DiskException as err:
|
||||||
debug(f"Unable to get disk from {device.path}: {err}")
|
debug(f'Unable to get disk from {device.path}: {err}')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
device_info = _DeviceInfo.from_disk(disk)
|
device_info = _DeviceInfo.from_disk(disk)
|
||||||
|
|
@ -105,7 +105,7 @@ class DeviceHandler:
|
||||||
lsblk_info = find_lsblk_info(partition.path, dev_lsblk_info.children)
|
lsblk_info = find_lsblk_info(partition.path, dev_lsblk_info.children)
|
||||||
|
|
||||||
if not lsblk_info:
|
if not lsblk_info:
|
||||||
debug(f"Partition lsblk info not found: {partition.path}")
|
debug(f'Partition lsblk info not found: {partition.path}')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
fs_type = self._determine_fs_type(partition, lsblk_info)
|
fs_type = self._determine_fs_type(partition, lsblk_info)
|
||||||
|
|
@ -133,20 +133,20 @@ class DeviceHandler:
|
||||||
devices = []
|
devices = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop_devices = SysCommand(["losetup", "-a"])
|
loop_devices = SysCommand(['losetup', '-a'])
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
debug(f"Failed to get loop devices: {err}")
|
debug(f'Failed to get loop devices: {err}')
|
||||||
else:
|
else:
|
||||||
for ld_info in str(loop_devices).splitlines():
|
for ld_info in str(loop_devices).splitlines():
|
||||||
try:
|
try:
|
||||||
loop_device_path, _ = ld_info.split(":", maxsplit=1)
|
loop_device_path, _ = ld_info.split(':', maxsplit=1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop_device = getDevice(loop_device_path)
|
loop_device = getDevice(loop_device_path)
|
||||||
except IOException as err:
|
except IOException as err:
|
||||||
debug(f"Failed to get loop device: {err}")
|
debug(f'Failed to get loop device: {err}')
|
||||||
else:
|
else:
|
||||||
devices.append(loop_device)
|
devices.append(loop_device)
|
||||||
|
|
||||||
|
|
@ -166,7 +166,7 @@ class DeviceHandler:
|
||||||
return FilesystemType(lsblk_info.fstype) if lsblk_info.fstype else None
|
return FilesystemType(lsblk_info.fstype) if lsblk_info.fstype else None
|
||||||
return None
|
return None
|
||||||
except ValueError:
|
except ValueError:
|
||||||
debug(f"Could not determine the filesystem: {partition.fileSystem}")
|
debug(f'Could not determine the filesystem: {partition.fileSystem}')
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -189,12 +189,12 @@ class DeviceHandler:
|
||||||
|
|
||||||
def get_parent_device_path(self, dev_path: Path) -> Path:
|
def get_parent_device_path(self, dev_path: Path) -> Path:
|
||||||
lsblk = get_lsblk_info(dev_path)
|
lsblk = get_lsblk_info(dev_path)
|
||||||
return Path(f"/dev/{lsblk.pkname}")
|
return Path(f'/dev/{lsblk.pkname}')
|
||||||
|
|
||||||
def get_unique_path_for_device(self, dev_path: Path) -> Path | None:
|
def get_unique_path_for_device(self, dev_path: Path) -> Path | None:
|
||||||
paths = Path("/dev/disk/by-id").glob("*")
|
paths = Path('/dev/disk/by-id').glob('*')
|
||||||
linked_targets = {p.resolve(): p for p in paths}
|
linked_targets = {p.resolve(): p for p in paths}
|
||||||
linked_wwn_targets = {p: linked_targets[p] for p in linked_targets if p.name.startswith("wwn-") or p.name.startswith("nvme-eui.")}
|
linked_wwn_targets = {p: linked_targets[p] for p in linked_targets if p.name.startswith('wwn-') or p.name.startswith('nvme-eui.')}
|
||||||
|
|
||||||
if dev_path in linked_wwn_targets:
|
if dev_path in linked_wwn_targets:
|
||||||
return linked_wwn_targets[dev_path]
|
return linked_wwn_targets[dev_path]
|
||||||
|
|
@ -234,9 +234,9 @@ class DeviceHandler:
|
||||||
mountpoint = Path(common_path)
|
mountpoint = Path(common_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = SysCommand(f"btrfs subvolume list {mountpoint}").decode()
|
result = SysCommand(f'btrfs subvolume list {mountpoint}').decode()
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
debug(f"Failed to read btrfs subvolume information: {err}")
|
debug(f'Failed to read btrfs subvolume information: {err}')
|
||||||
return subvol_infos
|
return subvol_infos
|
||||||
|
|
||||||
# It is assumed that lsblk will contain the fields as
|
# It is assumed that lsblk will contain the fields as
|
||||||
|
|
@ -250,8 +250,8 @@ class DeviceHandler:
|
||||||
for line in result.splitlines():
|
for line in result.splitlines():
|
||||||
# expected output format:
|
# expected output format:
|
||||||
# ID 257 gen 8 top level 5 path @home
|
# ID 257 gen 8 top level 5 path @home
|
||||||
name = Path(line.split(" ")[-1])
|
name = Path(line.split(' ')[-1])
|
||||||
sub_vol_mountpoint = btrfs_subvol_info.get("/" / name, None)
|
sub_vol_mountpoint = btrfs_subvol_info.get('/' / name, None)
|
||||||
subvol_infos.append(_BtrfsSubvolumeInfo(name, sub_vol_mountpoint))
|
subvol_infos.append(_BtrfsSubvolumeInfo(name, sub_vol_mountpoint))
|
||||||
|
|
||||||
if not lsblk_info.mountpoint:
|
if not lsblk_info.mountpoint:
|
||||||
|
|
@ -272,33 +272,33 @@ class DeviceHandler:
|
||||||
match fs_type:
|
match fs_type:
|
||||||
case FilesystemType.Btrfs | FilesystemType.F2fs | FilesystemType.Xfs:
|
case FilesystemType.Btrfs | FilesystemType.F2fs | FilesystemType.Xfs:
|
||||||
# Force overwrite
|
# Force overwrite
|
||||||
options.append("-f")
|
options.append('-f')
|
||||||
case FilesystemType.Ext2 | FilesystemType.Ext3 | FilesystemType.Ext4:
|
case FilesystemType.Ext2 | FilesystemType.Ext3 | FilesystemType.Ext4:
|
||||||
# Force create
|
# Force create
|
||||||
options.append("-F")
|
options.append('-F')
|
||||||
case FilesystemType.Fat12 | FilesystemType.Fat16 | FilesystemType.Fat32:
|
case FilesystemType.Fat12 | FilesystemType.Fat16 | FilesystemType.Fat32:
|
||||||
mkfs_type = "fat"
|
mkfs_type = 'fat'
|
||||||
# Set FAT size
|
# Set FAT size
|
||||||
options.extend(("-F", fs_type.value.removeprefix(mkfs_type)))
|
options.extend(('-F', fs_type.value.removeprefix(mkfs_type)))
|
||||||
case FilesystemType.Ntfs:
|
case FilesystemType.Ntfs:
|
||||||
# Skip zeroing and bad sector check
|
# Skip zeroing and bad sector check
|
||||||
options.append("--fast")
|
options.append('--fast')
|
||||||
case FilesystemType.LinuxSwap:
|
case FilesystemType.LinuxSwap:
|
||||||
command = "mkswap"
|
command = 'mkswap'
|
||||||
case _:
|
case _:
|
||||||
raise UnknownFilesystemFormat(f'Filetype "{fs_type.value}" is not supported')
|
raise UnknownFilesystemFormat(f'Filetype "{fs_type.value}" is not supported')
|
||||||
|
|
||||||
if not command:
|
if not command:
|
||||||
command = f"mkfs.{mkfs_type}"
|
command = f'mkfs.{mkfs_type}'
|
||||||
|
|
||||||
cmd = [command, *options, *additional_parted_options, str(path)]
|
cmd = [command, *options, *additional_parted_options, str(path)]
|
||||||
|
|
||||||
debug("Formatting filesystem:", " ".join(cmd))
|
debug('Formatting filesystem:', ' '.join(cmd))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
SysCommand(cmd)
|
SysCommand(cmd)
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
msg = f"Could not format {path} with {fs_type.value}: {err.message}"
|
msg = f'Could not format {path} with {fs_type.value}: {err.message}'
|
||||||
error(msg)
|
error(msg)
|
||||||
raise DiskError(msg) from err
|
raise DiskError(msg) from err
|
||||||
|
|
||||||
|
|
@ -322,10 +322,10 @@ class DeviceHandler:
|
||||||
luks_handler.unlock(key_file=key_file)
|
luks_handler.unlock(key_file=key_file)
|
||||||
|
|
||||||
if not luks_handler.mapper_dev:
|
if not luks_handler.mapper_dev:
|
||||||
raise DiskError("Failed to unlock luks device")
|
raise DiskError('Failed to unlock luks device')
|
||||||
|
|
||||||
if lock_after_create:
|
if lock_after_create:
|
||||||
debug(f"luks2 locking device: {dev_path}")
|
debug(f'luks2 locking device: {dev_path}')
|
||||||
luks_handler.lock()
|
luks_handler.lock()
|
||||||
|
|
||||||
return luks_handler
|
return luks_handler
|
||||||
|
|
@ -338,7 +338,7 @@ class DeviceHandler:
|
||||||
enc_conf: DiskEncryption,
|
enc_conf: DiskEncryption,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not enc_conf.encryption_password:
|
if not enc_conf.encryption_password:
|
||||||
raise ValueError("No encryption password provided")
|
raise ValueError('No encryption password provided')
|
||||||
|
|
||||||
luks_handler = Luks2(
|
luks_handler = Luks2(
|
||||||
dev_path,
|
dev_path,
|
||||||
|
|
@ -353,69 +353,69 @@ class DeviceHandler:
|
||||||
luks_handler.unlock(key_file=key_file)
|
luks_handler.unlock(key_file=key_file)
|
||||||
|
|
||||||
if not luks_handler.mapper_dev:
|
if not luks_handler.mapper_dev:
|
||||||
raise DiskError("Failed to unlock luks device")
|
raise DiskError('Failed to unlock luks device')
|
||||||
|
|
||||||
info(f"luks2 formatting mapper dev: {luks_handler.mapper_dev}")
|
info(f'luks2 formatting mapper dev: {luks_handler.mapper_dev}')
|
||||||
self.format(fs_type, luks_handler.mapper_dev)
|
self.format(fs_type, luks_handler.mapper_dev)
|
||||||
|
|
||||||
info(f"luks2 locking device: {dev_path}")
|
info(f'luks2 locking device: {dev_path}')
|
||||||
luks_handler.lock()
|
luks_handler.lock()
|
||||||
|
|
||||||
def _lvm_info(
|
def _lvm_info(
|
||||||
self,
|
self,
|
||||||
cmd: str,
|
cmd: str,
|
||||||
info_type: Literal["lv", "vg", "pvseg"],
|
info_type: Literal['lv', 'vg', 'pvseg'],
|
||||||
) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None:
|
) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None:
|
||||||
raw_info = SysCommand(cmd).decode().split("\n")
|
raw_info = SysCommand(cmd).decode().split('\n')
|
||||||
|
|
||||||
# for whatever reason the output sometimes contains
|
# for whatever reason the output sometimes contains
|
||||||
# "File descriptor X leaked leaked on vgs invocation
|
# "File descriptor X leaked leaked on vgs invocation
|
||||||
data = "\n".join([raw for raw in raw_info if "File descriptor" not in raw])
|
data = '\n'.join([raw for raw in raw_info if 'File descriptor' not in raw])
|
||||||
|
|
||||||
debug(f"LVM info: {data}")
|
debug(f'LVM info: {data}')
|
||||||
|
|
||||||
reports = json.loads(data)
|
reports = json.loads(data)
|
||||||
|
|
||||||
for report in reports["report"]:
|
for report in reports['report']:
|
||||||
if len(report[info_type]) != 1:
|
if len(report[info_type]) != 1:
|
||||||
raise ValueError("Report does not contain any entry")
|
raise ValueError('Report does not contain any entry')
|
||||||
|
|
||||||
entry = report[info_type][0]
|
entry = report[info_type][0]
|
||||||
|
|
||||||
match info_type:
|
match info_type:
|
||||||
case "pvseg":
|
case 'pvseg':
|
||||||
return LvmPVInfo(
|
return LvmPVInfo(
|
||||||
pv_name=Path(entry["pv_name"]),
|
pv_name=Path(entry['pv_name']),
|
||||||
lv_name=entry["lv_name"],
|
lv_name=entry['lv_name'],
|
||||||
vg_name=entry["vg_name"],
|
vg_name=entry['vg_name'],
|
||||||
)
|
)
|
||||||
case "lv":
|
case 'lv':
|
||||||
return LvmVolumeInfo(
|
return LvmVolumeInfo(
|
||||||
lv_name=entry["lv_name"],
|
lv_name=entry['lv_name'],
|
||||||
vg_name=entry["vg_name"],
|
vg_name=entry['vg_name'],
|
||||||
lv_size=Size(int(entry["lv_size"][:-1]), Unit.B, SectorSize.default()),
|
lv_size=Size(int(entry['lv_size'][:-1]), Unit.B, SectorSize.default()),
|
||||||
)
|
)
|
||||||
case "vg":
|
case 'vg':
|
||||||
return LvmGroupInfo(
|
return LvmGroupInfo(
|
||||||
vg_uuid=entry["vg_uuid"],
|
vg_uuid=entry['vg_uuid'],
|
||||||
vg_size=Size(int(entry["vg_size"][:-1]), Unit.B, SectorSize.default()),
|
vg_size=Size(int(entry['vg_size'][:-1]), Unit.B, SectorSize.default()),
|
||||||
)
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def _lvm_info_with_retry(self, cmd: str, info_type: Literal["lv"]) -> LvmVolumeInfo | None: ...
|
def _lvm_info_with_retry(self, cmd: str, info_type: Literal['lv']) -> LvmVolumeInfo | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def _lvm_info_with_retry(self, cmd: str, info_type: Literal["vg"]) -> LvmGroupInfo | None: ...
|
def _lvm_info_with_retry(self, cmd: str, info_type: Literal['vg']) -> LvmGroupInfo | None: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def _lvm_info_with_retry(self, cmd: str, info_type: Literal["pvseg"]) -> LvmPVInfo | None: ...
|
def _lvm_info_with_retry(self, cmd: str, info_type: Literal['pvseg']) -> LvmPVInfo | None: ...
|
||||||
|
|
||||||
def _lvm_info_with_retry(
|
def _lvm_info_with_retry(
|
||||||
self,
|
self,
|
||||||
cmd: str,
|
cmd: str,
|
||||||
info_type: Literal["lv", "vg", "pvseg"],
|
info_type: Literal['lv', 'vg', 'pvseg'],
|
||||||
) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None:
|
) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|
@ -424,63 +424,63 @@ class DeviceHandler:
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
def lvm_vol_info(self, lv_name: str) -> LvmVolumeInfo | None:
|
def lvm_vol_info(self, lv_name: str) -> LvmVolumeInfo | None:
|
||||||
cmd = f"lvs --reportformat json --unit B -S lv_name={lv_name}"
|
cmd = f'lvs --reportformat json --unit B -S lv_name={lv_name}'
|
||||||
|
|
||||||
return self._lvm_info_with_retry(cmd, "lv")
|
return self._lvm_info_with_retry(cmd, 'lv')
|
||||||
|
|
||||||
def lvm_group_info(self, vg_name: str) -> LvmGroupInfo | None:
|
def lvm_group_info(self, vg_name: str) -> LvmGroupInfo | None:
|
||||||
cmd = f"vgs --reportformat json --unit B -o vg_name,vg_uuid,vg_size -S vg_name={vg_name}"
|
cmd = f'vgs --reportformat json --unit B -o vg_name,vg_uuid,vg_size -S vg_name={vg_name}'
|
||||||
|
|
||||||
return self._lvm_info_with_retry(cmd, "vg")
|
return self._lvm_info_with_retry(cmd, 'vg')
|
||||||
|
|
||||||
def lvm_pvseg_info(self, vg_name: str, lv_name: str) -> LvmPVInfo | None:
|
def lvm_pvseg_info(self, vg_name: str, lv_name: str) -> LvmPVInfo | None:
|
||||||
cmd = f"pvs --segments -o+lv_name,vg_name -S vg_name={vg_name},lv_name={lv_name} --reportformat json "
|
cmd = f'pvs --segments -o+lv_name,vg_name -S vg_name={vg_name},lv_name={lv_name} --reportformat json '
|
||||||
|
|
||||||
return self._lvm_info_with_retry(cmd, "pvseg")
|
return self._lvm_info_with_retry(cmd, 'pvseg')
|
||||||
|
|
||||||
def lvm_vol_change(self, vol: LvmVolume, activate: bool) -> None:
|
def lvm_vol_change(self, vol: LvmVolume, activate: bool) -> None:
|
||||||
active_flag = "y" if activate else "n"
|
active_flag = 'y' if activate else 'n'
|
||||||
cmd = f"lvchange -a {active_flag} {vol.safe_dev_path}"
|
cmd = f'lvchange -a {active_flag} {vol.safe_dev_path}'
|
||||||
|
|
||||||
debug(f"lvchange volume: {cmd}")
|
debug(f'lvchange volume: {cmd}')
|
||||||
SysCommand(cmd)
|
SysCommand(cmd)
|
||||||
|
|
||||||
def lvm_export_vg(self, vg: LvmVolumeGroup) -> None:
|
def lvm_export_vg(self, vg: LvmVolumeGroup) -> None:
|
||||||
cmd = f"vgexport {vg.name}"
|
cmd = f'vgexport {vg.name}'
|
||||||
|
|
||||||
debug(f"vgexport: {cmd}")
|
debug(f'vgexport: {cmd}')
|
||||||
SysCommand(cmd)
|
SysCommand(cmd)
|
||||||
|
|
||||||
def lvm_import_vg(self, vg: LvmVolumeGroup) -> None:
|
def lvm_import_vg(self, vg: LvmVolumeGroup) -> None:
|
||||||
cmd = f"vgimport {vg.name}"
|
cmd = f'vgimport {vg.name}'
|
||||||
|
|
||||||
debug(f"vgimport: {cmd}")
|
debug(f'vgimport: {cmd}')
|
||||||
SysCommand(cmd)
|
SysCommand(cmd)
|
||||||
|
|
||||||
def lvm_vol_reduce(self, vol_path: Path, amount: Size) -> None:
|
def lvm_vol_reduce(self, vol_path: Path, amount: Size) -> None:
|
||||||
val = amount.format_size(Unit.B, include_unit=False)
|
val = amount.format_size(Unit.B, include_unit=False)
|
||||||
cmd = f"lvreduce -L -{val}B {vol_path}"
|
cmd = f'lvreduce -L -{val}B {vol_path}'
|
||||||
|
|
||||||
debug(f"Reducing LVM volume size: {cmd}")
|
debug(f'Reducing LVM volume size: {cmd}')
|
||||||
SysCommand(cmd)
|
SysCommand(cmd)
|
||||||
|
|
||||||
def lvm_pv_create(self, pvs: Iterable[Path]) -> None:
|
def lvm_pv_create(self, pvs: Iterable[Path]) -> None:
|
||||||
cmd = "pvcreate " + " ".join([str(pv) for pv in pvs])
|
cmd = 'pvcreate ' + ' '.join([str(pv) for pv in pvs])
|
||||||
debug(f"Creating LVM PVS: {cmd}")
|
debug(f'Creating LVM PVS: {cmd}')
|
||||||
|
|
||||||
worker = SysCommandWorker(cmd)
|
worker = SysCommandWorker(cmd)
|
||||||
worker.poll()
|
worker.poll()
|
||||||
worker.write(b"y\n", line_ending=False)
|
worker.write(b'y\n', line_ending=False)
|
||||||
|
|
||||||
def lvm_vg_create(self, pvs: Iterable[Path], vg_name: str) -> None:
|
def lvm_vg_create(self, pvs: Iterable[Path], vg_name: str) -> None:
|
||||||
pvs_str = " ".join([str(pv) for pv in pvs])
|
pvs_str = ' '.join([str(pv) for pv in pvs])
|
||||||
cmd = f"vgcreate --yes {vg_name} {pvs_str}"
|
cmd = f'vgcreate --yes {vg_name} {pvs_str}'
|
||||||
|
|
||||||
debug(f"Creating LVM group: {cmd}")
|
debug(f'Creating LVM group: {cmd}')
|
||||||
|
|
||||||
worker = SysCommandWorker(cmd)
|
worker = SysCommandWorker(cmd)
|
||||||
worker.poll()
|
worker.poll()
|
||||||
worker.write(b"y\n", line_ending=False)
|
worker.write(b'y\n', line_ending=False)
|
||||||
|
|
||||||
def lvm_vol_create(self, vg_name: str, volume: LvmVolume, offset: Size | None = None) -> None:
|
def lvm_vol_create(self, vg_name: str, volume: LvmVolume, offset: Size | None = None) -> None:
|
||||||
if offset is not None:
|
if offset is not None:
|
||||||
|
|
@ -489,16 +489,16 @@ class DeviceHandler:
|
||||||
length = volume.length
|
length = volume.length
|
||||||
|
|
||||||
length_str = length.format_size(Unit.B, include_unit=False)
|
length_str = length.format_size(Unit.B, include_unit=False)
|
||||||
cmd = f"lvcreate --yes -L {length_str}B {vg_name} -n {volume.name}"
|
cmd = f'lvcreate --yes -L {length_str}B {vg_name} -n {volume.name}'
|
||||||
|
|
||||||
debug(f"Creating volume: {cmd}")
|
debug(f'Creating volume: {cmd}')
|
||||||
|
|
||||||
worker = SysCommandWorker(cmd)
|
worker = SysCommandWorker(cmd)
|
||||||
worker.poll()
|
worker.poll()
|
||||||
worker.write(b"y\n", line_ending=False)
|
worker.write(b'y\n', line_ending=False)
|
||||||
|
|
||||||
volume.vg_name = vg_name
|
volume.vg_name = vg_name
|
||||||
volume.dev_path = Path(f"/dev/{vg_name}/{volume.name}")
|
volume.dev_path = Path(f'/dev/{vg_name}/{volume.name}')
|
||||||
|
|
||||||
def _setup_partition(
|
def _setup_partition(
|
||||||
self,
|
self,
|
||||||
|
|
@ -510,11 +510,11 @@ class DeviceHandler:
|
||||||
# when we require a delete and the partition to be (re)created
|
# when we require a delete and the partition to be (re)created
|
||||||
# already exists then we have to delete it first
|
# already exists then we have to delete it first
|
||||||
if requires_delete and part_mod.status in [ModificationStatus.Modify, ModificationStatus.Delete]:
|
if requires_delete and part_mod.status in [ModificationStatus.Modify, ModificationStatus.Delete]:
|
||||||
info(f"Delete existing partition: {part_mod.safe_dev_path}")
|
info(f'Delete existing partition: {part_mod.safe_dev_path}')
|
||||||
part_info = self.find_partition(part_mod.safe_dev_path)
|
part_info = self.find_partition(part_mod.safe_dev_path)
|
||||||
|
|
||||||
if not part_info:
|
if not part_info:
|
||||||
raise DiskError(f"No partition for dev path found: {part_mod.safe_dev_path}")
|
raise DiskError(f'No partition for dev path found: {part_mod.safe_dev_path}')
|
||||||
|
|
||||||
disk.deletePartition(part_info.partition)
|
disk.deletePartition(part_info.partition)
|
||||||
|
|
||||||
|
|
@ -550,14 +550,14 @@ class DeviceHandler:
|
||||||
for flag in part_mod.flags:
|
for flag in part_mod.flags:
|
||||||
partition.setFlag(flag.flag_id)
|
partition.setFlag(flag.flag_id)
|
||||||
|
|
||||||
debug(f"\tType: {part_mod.type.value}")
|
debug(f'\tType: {part_mod.type.value}')
|
||||||
debug(f"\tFilesystem: {fs_value}")
|
debug(f'\tFilesystem: {fs_value}')
|
||||||
debug(f"\tGeometry: {start_sector.value} start sector, {length_sector.value} length")
|
debug(f'\tGeometry: {start_sector.value} start sector, {length_sector.value} length')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
disk.addPartition(partition=partition, constraint=disk.device.optimalAlignedConstraint)
|
disk.addPartition(partition=partition, constraint=disk.device.optimalAlignedConstraint)
|
||||||
except PartitionException as ex:
|
except PartitionException as ex:
|
||||||
raise DiskError(f"Unable to add partition, most likely due to overlapping sectors: {ex}") from ex
|
raise DiskError(f'Unable to add partition, most likely due to overlapping sectors: {ex}') from ex
|
||||||
|
|
||||||
if disk.type == PartitionTable.GPT.value:
|
if disk.type == PartitionTable.GPT.value:
|
||||||
if part_mod.is_root():
|
if part_mod.is_root():
|
||||||
|
|
@ -572,18 +572,18 @@ class DeviceHandler:
|
||||||
lsblk_info = get_lsblk_info(path)
|
lsblk_info = get_lsblk_info(path)
|
||||||
|
|
||||||
if not lsblk_info.partn:
|
if not lsblk_info.partn:
|
||||||
debug(f"Unable to determine new partition number: {path}\n{lsblk_info}")
|
debug(f'Unable to determine new partition number: {path}\n{lsblk_info}')
|
||||||
raise DiskError(f"Unable to determine new partition number: {path}")
|
raise DiskError(f'Unable to determine new partition number: {path}')
|
||||||
|
|
||||||
if not lsblk_info.partuuid:
|
if not lsblk_info.partuuid:
|
||||||
debug(f"Unable to determine new partition uuid: {path}\n{lsblk_info}")
|
debug(f'Unable to determine new partition uuid: {path}\n{lsblk_info}')
|
||||||
raise DiskError(f"Unable to determine new partition uuid: {path}")
|
raise DiskError(f'Unable to determine new partition uuid: {path}')
|
||||||
|
|
||||||
if not lsblk_info.uuid:
|
if not lsblk_info.uuid:
|
||||||
debug(f"Unable to determine new uuid: {path}\n{lsblk_info}")
|
debug(f'Unable to determine new uuid: {path}\n{lsblk_info}')
|
||||||
raise DiskError(f"Unable to determine new uuid: {path}")
|
raise DiskError(f'Unable to determine new uuid: {path}')
|
||||||
|
|
||||||
debug(f"partition information found: {lsblk_info.model_dump_json()}")
|
debug(f'partition information found: {lsblk_info.model_dump_json()}')
|
||||||
|
|
||||||
return lsblk_info
|
return lsblk_info
|
||||||
|
|
||||||
|
|
@ -593,28 +593,28 @@ class DeviceHandler:
|
||||||
btrfs_subvols: list[SubvolumeModification],
|
btrfs_subvols: list[SubvolumeModification],
|
||||||
mount_options: list[str],
|
mount_options: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
info(f"Creating subvolumes: {path}")
|
info(f'Creating subvolumes: {path}')
|
||||||
|
|
||||||
self.mount(path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True)
|
self.mount(path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True)
|
||||||
|
|
||||||
for sub_vol in sorted(btrfs_subvols, key=lambda x: x.name):
|
for sub_vol in sorted(btrfs_subvols, key=lambda x: x.name):
|
||||||
debug(f"Creating subvolume: {sub_vol.name}")
|
debug(f'Creating subvolume: {sub_vol.name}')
|
||||||
|
|
||||||
subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name
|
subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name
|
||||||
|
|
||||||
SysCommand(f"btrfs subvolume create -p {subvol_path}")
|
SysCommand(f'btrfs subvolume create -p {subvol_path}')
|
||||||
|
|
||||||
if BtrfsMountOption.nodatacow.value in mount_options:
|
if BtrfsMountOption.nodatacow.value in mount_options:
|
||||||
try:
|
try:
|
||||||
SysCommand(f"chattr +C {subvol_path}")
|
SysCommand(f'chattr +C {subvol_path}')
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
raise DiskError(f"Could not set nodatacow attribute at {subvol_path}: {err}")
|
raise DiskError(f'Could not set nodatacow attribute at {subvol_path}: {err}')
|
||||||
|
|
||||||
if BtrfsMountOption.compress.value in mount_options:
|
if BtrfsMountOption.compress.value in mount_options:
|
||||||
try:
|
try:
|
||||||
SysCommand(f"chattr +c {subvol_path}")
|
SysCommand(f'chattr +c {subvol_path}')
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
raise DiskError(f"Could not set compress attribute at {subvol_path}: {err}")
|
raise DiskError(f'Could not set compress attribute at {subvol_path}: {err}')
|
||||||
|
|
||||||
umount(path)
|
umount(path)
|
||||||
|
|
||||||
|
|
@ -623,12 +623,12 @@ class DeviceHandler:
|
||||||
part_mod: PartitionModification,
|
part_mod: PartitionModification,
|
||||||
enc_conf: DiskEncryption | None = None,
|
enc_conf: DiskEncryption | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
info(f"Creating subvolumes: {part_mod.safe_dev_path}")
|
info(f'Creating subvolumes: {part_mod.safe_dev_path}')
|
||||||
|
|
||||||
# unlock the partition first if it's encrypted
|
# unlock the partition first if it's encrypted
|
||||||
if enc_conf is not None and part_mod in enc_conf.partitions:
|
if enc_conf is not None and part_mod in enc_conf.partitions:
|
||||||
if not part_mod.mapper_name:
|
if not part_mod.mapper_name:
|
||||||
raise ValueError("No device path specified for modification")
|
raise ValueError('No device path specified for modification')
|
||||||
|
|
||||||
luks_handler = self.unlock_luks2_dev(
|
luks_handler = self.unlock_luks2_dev(
|
||||||
part_mod.safe_dev_path,
|
part_mod.safe_dev_path,
|
||||||
|
|
@ -637,7 +637,7 @@ class DeviceHandler:
|
||||||
)
|
)
|
||||||
|
|
||||||
if not luks_handler.mapper_dev:
|
if not luks_handler.mapper_dev:
|
||||||
raise DiskError("Failed to unlock luks device")
|
raise DiskError('Failed to unlock luks device')
|
||||||
|
|
||||||
dev_path = luks_handler.mapper_dev
|
dev_path = luks_handler.mapper_dev
|
||||||
else:
|
else:
|
||||||
|
|
@ -652,11 +652,11 @@ class DeviceHandler:
|
||||||
)
|
)
|
||||||
|
|
||||||
for sub_vol in sorted(part_mod.btrfs_subvols, key=lambda x: x.name):
|
for sub_vol in sorted(part_mod.btrfs_subvols, key=lambda x: x.name):
|
||||||
debug(f"Creating subvolume: {sub_vol.name}")
|
debug(f'Creating subvolume: {sub_vol.name}')
|
||||||
|
|
||||||
subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name
|
subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name
|
||||||
|
|
||||||
SysCommand(f"btrfs subvolume create -p {subvol_path}")
|
SysCommand(f'btrfs subvolume create -p {subvol_path}')
|
||||||
|
|
||||||
umount(dev_path)
|
umount(dev_path)
|
||||||
|
|
||||||
|
|
@ -675,17 +675,17 @@ class DeviceHandler:
|
||||||
luks_handler.unlock()
|
luks_handler.unlock()
|
||||||
|
|
||||||
if not luks_handler.is_unlocked():
|
if not luks_handler.is_unlocked():
|
||||||
raise DiskError(f"Failed to unlock luks2 device: {dev_path}")
|
raise DiskError(f'Failed to unlock luks2 device: {dev_path}')
|
||||||
|
|
||||||
return luks_handler
|
return luks_handler
|
||||||
|
|
||||||
def umount_all_existing(self, device_path: Path) -> None:
|
def umount_all_existing(self, device_path: Path) -> None:
|
||||||
debug(f"Unmounting all existing partitions: {device_path}")
|
debug(f'Unmounting all existing partitions: {device_path}')
|
||||||
|
|
||||||
existing_partitions = self._devices[device_path].partition_infos
|
existing_partitions = self._devices[device_path].partition_infos
|
||||||
|
|
||||||
for partition in existing_partitions:
|
for partition in existing_partitions:
|
||||||
debug(f"Unmounting: {partition.path}")
|
debug(f'Unmounting: {partition.path}')
|
||||||
|
|
||||||
# un-mount for existing encrypted partitions
|
# un-mount for existing encrypted partitions
|
||||||
if partition.fs_type == FilesystemType.Crypto_luks:
|
if partition.fs_type == FilesystemType.Crypto_luks:
|
||||||
|
|
@ -706,15 +706,15 @@ class DeviceHandler:
|
||||||
# WARNING: the entire device will be wiped and all data lost
|
# WARNING: the entire device will be wiped and all data lost
|
||||||
if modification.wipe:
|
if modification.wipe:
|
||||||
if partition_table.is_mbr() and len(modification.partitions) > 3:
|
if partition_table.is_mbr() and len(modification.partitions) > 3:
|
||||||
raise DiskError("Too many partitions on disk, MBR disks can only have 3 primary partitions")
|
raise DiskError('Too many partitions on disk, MBR disks can only have 3 primary partitions')
|
||||||
|
|
||||||
self.wipe_dev(modification.device)
|
self.wipe_dev(modification.device)
|
||||||
disk = freshDisk(modification.device.disk.device, partition_table.value)
|
disk = freshDisk(modification.device.disk.device, partition_table.value)
|
||||||
else:
|
else:
|
||||||
info(f"Use existing device: {modification.device_path}")
|
info(f'Use existing device: {modification.device_path}')
|
||||||
disk = modification.device.disk
|
disk = modification.device.disk
|
||||||
|
|
||||||
info(f"Creating partitions: {modification.device_path}")
|
info(f'Creating partitions: {modification.device_path}')
|
||||||
|
|
||||||
# don't touch existing partitions
|
# don't touch existing partitions
|
||||||
filtered_part = [p for p in modification.partitions if not p.exists()]
|
filtered_part = [p for p in modification.partitions if not p.exists()]
|
||||||
|
|
@ -730,9 +730,9 @@ class DeviceHandler:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def swapon(path: Path) -> None:
|
def swapon(path: Path) -> None:
|
||||||
try:
|
try:
|
||||||
SysCommand(["swapon", str(path)])
|
SysCommand(['swapon', str(path)])
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
raise DiskError(f"Could not enable swap {path}:\n{err.message}")
|
raise DiskError(f'Could not enable swap {path}:\n{err.message}')
|
||||||
|
|
||||||
def mount(
|
def mount(
|
||||||
self,
|
self,
|
||||||
|
|
@ -746,30 +746,30 @@ class DeviceHandler:
|
||||||
target_mountpoint.mkdir(parents=True, exist_ok=True)
|
target_mountpoint.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
if not target_mountpoint.exists():
|
if not target_mountpoint.exists():
|
||||||
raise ValueError("Target mountpoint does not exist")
|
raise ValueError('Target mountpoint does not exist')
|
||||||
|
|
||||||
lsblk_info = get_lsblk_info(dev_path)
|
lsblk_info = get_lsblk_info(dev_path)
|
||||||
if target_mountpoint in lsblk_info.mountpoints:
|
if target_mountpoint in lsblk_info.mountpoints:
|
||||||
info(f"Device already mounted at {target_mountpoint}")
|
info(f'Device already mounted at {target_mountpoint}')
|
||||||
return
|
return
|
||||||
|
|
||||||
cmd = ["mount"]
|
cmd = ['mount']
|
||||||
|
|
||||||
if len(options):
|
if len(options):
|
||||||
cmd.extend(("-o", ",".join(options)))
|
cmd.extend(('-o', ','.join(options)))
|
||||||
if mount_fs:
|
if mount_fs:
|
||||||
cmd.extend(("-t", mount_fs))
|
cmd.extend(('-t', mount_fs))
|
||||||
|
|
||||||
cmd.extend((str(dev_path), str(target_mountpoint)))
|
cmd.extend((str(dev_path), str(target_mountpoint)))
|
||||||
|
|
||||||
command = " ".join(cmd)
|
command = ' '.join(cmd)
|
||||||
|
|
||||||
debug(f"Mounting {dev_path}: {command}")
|
debug(f'Mounting {dev_path}: {command}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
SysCommand(command)
|
SysCommand(command)
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
raise DiskError(f"Could not mount {dev_path}: {command}\n{err.message}")
|
raise DiskError(f'Could not mount {dev_path}: {command}\n{err.message}')
|
||||||
|
|
||||||
def detect_pre_mounted_mods(self, base_mountpoint: Path) -> list[DeviceModification]:
|
def detect_pre_mounted_mods(self, base_mountpoint: Path) -> list[DeviceModification]:
|
||||||
part_mods: dict[Path, list[PartitionModification]] = {}
|
part_mods: dict[Path, list[PartitionModification]] = {}
|
||||||
|
|
@ -799,16 +799,16 @@ class DeviceHandler:
|
||||||
|
|
||||||
def partprobe(self, path: Path | None = None) -> None:
|
def partprobe(self, path: Path | None = None) -> None:
|
||||||
if path is not None:
|
if path is not None:
|
||||||
command = f"partprobe {path}"
|
command = f'partprobe {path}'
|
||||||
else:
|
else:
|
||||||
command = "partprobe"
|
command = 'partprobe'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
debug(f"Calling partprobe: {command}")
|
debug(f'Calling partprobe: {command}')
|
||||||
SysCommand(command)
|
SysCommand(command)
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
if "have been written, but we have been unable to inform the kernel of the change" in str(err):
|
if 'have been written, but we have been unable to inform the kernel of the change' in str(err):
|
||||||
log(f"Partprobe was not able to inform the kernel of the new disk state (ignoring error): {err}", fg="gray", level=logging.INFO)
|
log(f'Partprobe was not able to inform the kernel of the new disk state (ignoring error): {err}', fg='gray', level=logging.INFO)
|
||||||
else:
|
else:
|
||||||
error(f'"{command}" failed to run (continuing anyway): {err}')
|
error(f'"{command}" failed to run (continuing anyway): {err}')
|
||||||
|
|
||||||
|
|
@ -818,7 +818,7 @@ class DeviceHandler:
|
||||||
@param dev_path: Device path of the partition to be wiped.
|
@param dev_path: Device path of the partition to be wiped.
|
||||||
@type dev_path: str
|
@type dev_path: str
|
||||||
"""
|
"""
|
||||||
with open(dev_path, "wb") as p:
|
with open(dev_path, 'wb') as p:
|
||||||
p.write(bytearray(1024))
|
p.write(bytearray(1024))
|
||||||
|
|
||||||
def wipe_dev(self, block_device: BDevice) -> None:
|
def wipe_dev(self, block_device: BDevice) -> None:
|
||||||
|
|
@ -827,7 +827,7 @@ class DeviceHandler:
|
||||||
This is not intended to be secure, but rather to ensure that
|
This is not intended to be secure, but rather to ensure that
|
||||||
auto-discovery tools don't recognize anything here.
|
auto-discovery tools don't recognize anything here.
|
||||||
"""
|
"""
|
||||||
info(f"Wiping partitions and metadata: {block_device.device_info.path}")
|
info(f'Wiping partitions and metadata: {block_device.device_info.path}')
|
||||||
|
|
||||||
for partition in block_device.partition_infos:
|
for partition in block_device.partition_infos:
|
||||||
luks = Luks2(partition.path)
|
luks = Luks2(partition.path)
|
||||||
|
|
@ -841,9 +841,9 @@ class DeviceHandler:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def udev_sync() -> None:
|
def udev_sync() -> None:
|
||||||
try:
|
try:
|
||||||
SysCommand("udevadm settle")
|
SysCommand('udevadm settle')
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
debug(f"Failed to synchronize with udev: {err}")
|
debug(f'Failed to synchronize with udev: {err}')
|
||||||
|
|
||||||
|
|
||||||
device_handler = DeviceHandler()
|
device_handler = DeviceHandler()
|
||||||
|
|
|
||||||
|
|
@ -38,19 +38,19 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
||||||
def _define_menu_options(self) -> list[MenuItem]:
|
def _define_menu_options(self) -> list[MenuItem]:
|
||||||
return [
|
return [
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Partitioning"),
|
text=tr('Partitioning'),
|
||||||
action=self._select_disk_layout_config,
|
action=self._select_disk_layout_config,
|
||||||
value=self._disk_menu_config.disk_config,
|
value=self._disk_menu_config.disk_config,
|
||||||
preview_action=self._prev_disk_layouts,
|
preview_action=self._prev_disk_layouts,
|
||||||
key="disk_config",
|
key='disk_config',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text="LVM (BETA)",
|
text='LVM (BETA)',
|
||||||
action=self._select_lvm_config,
|
action=self._select_lvm_config,
|
||||||
value=self._disk_menu_config.lvm_config,
|
value=self._disk_menu_config.lvm_config,
|
||||||
preview_action=self._prev_lvm_config,
|
preview_action=self._prev_lvm_config,
|
||||||
dependencies=[self._check_dep_lvm],
|
dependencies=[self._check_dep_lvm],
|
||||||
key="lvm_config",
|
key='lvm_config',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _check_dep_lvm(self) -> bool:
|
def _check_dep_lvm(self) -> bool:
|
||||||
disk_layout_conf: DiskLayoutConfiguration | None = self._menu_item_group.find_by_key("disk_config").value
|
disk_layout_conf: DiskLayoutConfiguration | None = self._menu_item_group.find_by_key('disk_config').value
|
||||||
|
|
||||||
if disk_layout_conf and disk_layout_conf.config_type == DiskLayoutType.Default:
|
if disk_layout_conf and disk_layout_conf.config_type == DiskLayoutType.Default:
|
||||||
return True
|
return True
|
||||||
|
|
@ -79,12 +79,12 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
||||||
disk_config = select_disk_config(preset)
|
disk_config = select_disk_config(preset)
|
||||||
|
|
||||||
if disk_config != preset:
|
if disk_config != preset:
|
||||||
self._menu_item_group.find_by_key("lvm_config").value = None
|
self._menu_item_group.find_by_key('lvm_config').value = None
|
||||||
|
|
||||||
return disk_config
|
return disk_config
|
||||||
|
|
||||||
def _select_lvm_config(self, preset: LvmConfiguration | None) -> LvmConfiguration | None:
|
def _select_lvm_config(self, preset: LvmConfiguration | None) -> LvmConfiguration | None:
|
||||||
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key("disk_config").value
|
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
|
||||||
|
|
||||||
if disk_config:
|
if disk_config:
|
||||||
return select_lvm_config(disk_config, preset=preset)
|
return select_lvm_config(disk_config, preset=preset)
|
||||||
|
|
@ -98,28 +98,28 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
||||||
disk_layout_conf = item.get_value()
|
disk_layout_conf = item.get_value()
|
||||||
|
|
||||||
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
|
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
|
||||||
msg = tr("Configuration type: {}").format(disk_layout_conf.config_type.display_msg()) + "\n"
|
msg = tr('Configuration type: {}').format(disk_layout_conf.config_type.display_msg()) + '\n'
|
||||||
msg += tr("Mountpoint") + ": " + str(disk_layout_conf.mountpoint)
|
msg += tr('Mountpoint') + ': ' + str(disk_layout_conf.mountpoint)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
device_mods = [d for d in disk_layout_conf.device_modifications if d.partitions]
|
device_mods = [d for d in disk_layout_conf.device_modifications if d.partitions]
|
||||||
|
|
||||||
if device_mods:
|
if device_mods:
|
||||||
output_partition = "{}: {}\n".format(tr("Configuration"), disk_layout_conf.config_type.display_msg())
|
output_partition = '{}: {}\n'.format(tr('Configuration'), disk_layout_conf.config_type.display_msg())
|
||||||
output_btrfs = ""
|
output_btrfs = ''
|
||||||
|
|
||||||
for mod in device_mods:
|
for mod in device_mods:
|
||||||
# create partition table
|
# create partition table
|
||||||
partition_table = FormattedOutput.as_table(mod.partitions)
|
partition_table = FormattedOutput.as_table(mod.partitions)
|
||||||
|
|
||||||
output_partition += f"{mod.device_path}: {mod.device.device_info.model}\n"
|
output_partition += f'{mod.device_path}: {mod.device.device_info.model}\n'
|
||||||
output_partition += "{}: {}\n".format(tr("Wipe"), mod.wipe)
|
output_partition += '{}: {}\n'.format(tr('Wipe'), mod.wipe)
|
||||||
output_partition += partition_table + "\n"
|
output_partition += partition_table + '\n'
|
||||||
|
|
||||||
# create btrfs table
|
# create btrfs table
|
||||||
btrfs_partitions = [p for p in mod.partitions if p.btrfs_subvols]
|
btrfs_partitions = [p for p in mod.partitions if p.btrfs_subvols]
|
||||||
for partition in btrfs_partitions:
|
for partition in btrfs_partitions:
|
||||||
output_btrfs += FormattedOutput.as_table(partition.btrfs_subvols) + "\n"
|
output_btrfs += FormattedOutput.as_table(partition.btrfs_subvols) + '\n'
|
||||||
|
|
||||||
output = output_partition + output_btrfs
|
output = output_partition + output_btrfs
|
||||||
return output.rstrip()
|
return output.rstrip()
|
||||||
|
|
@ -132,16 +132,16 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
||||||
|
|
||||||
lvm_config: LvmConfiguration = item.value
|
lvm_config: LvmConfiguration = item.value
|
||||||
|
|
||||||
output = "{}: {}\n".format(tr("Configuration"), lvm_config.config_type.display_msg())
|
output = '{}: {}\n'.format(tr('Configuration'), lvm_config.config_type.display_msg())
|
||||||
|
|
||||||
for vol_gp in lvm_config.vol_groups:
|
for vol_gp in lvm_config.vol_groups:
|
||||||
pv_table = FormattedOutput.as_table(vol_gp.pvs)
|
pv_table = FormattedOutput.as_table(vol_gp.pvs)
|
||||||
output += "{}:\n{}".format(tr("Physical volumes"), pv_table)
|
output += '{}:\n{}'.format(tr('Physical volumes'), pv_table)
|
||||||
|
|
||||||
output += f"\nVolume Group: {vol_gp.name}"
|
output += f'\nVolume Group: {vol_gp.name}'
|
||||||
|
|
||||||
lvm_volumes = FormattedOutput.as_table(vol_gp.volumes)
|
lvm_volumes = FormattedOutput.as_table(vol_gp.volumes)
|
||||||
output += "\n\n{}:\n{}".format(tr("Volumes"), lvm_volumes)
|
output += '\n\n{}:\n{}'.format(tr('Volumes'), lvm_volumes)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,43 +50,43 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
||||||
def _define_menu_options(self) -> list[MenuItem]:
|
def _define_menu_options(self) -> list[MenuItem]:
|
||||||
return [
|
return [
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Encryption type"),
|
text=tr('Encryption type'),
|
||||||
action=lambda x: select_encryption_type(self._disk_config, x),
|
action=lambda x: select_encryption_type(self._disk_config, x),
|
||||||
value=self._enc_config.encryption_type,
|
value=self._enc_config.encryption_type,
|
||||||
preview_action=self._preview,
|
preview_action=self._preview,
|
||||||
key="encryption_type",
|
key='encryption_type',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Encryption password"),
|
text=tr('Encryption password'),
|
||||||
action=lambda x: select_encrypted_password(),
|
action=lambda x: select_encrypted_password(),
|
||||||
value=self._enc_config.encryption_password,
|
value=self._enc_config.encryption_password,
|
||||||
dependencies=[self._check_dep_enc_type],
|
dependencies=[self._check_dep_enc_type],
|
||||||
preview_action=self._preview,
|
preview_action=self._preview,
|
||||||
key="encryption_password",
|
key='encryption_password',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Partitions"),
|
text=tr('Partitions'),
|
||||||
action=lambda x: select_partitions_to_encrypt(self._disk_config.device_modifications, x),
|
action=lambda x: select_partitions_to_encrypt(self._disk_config.device_modifications, x),
|
||||||
value=self._enc_config.partitions,
|
value=self._enc_config.partitions,
|
||||||
dependencies=[self._check_dep_partitions],
|
dependencies=[self._check_dep_partitions],
|
||||||
preview_action=self._preview,
|
preview_action=self._preview,
|
||||||
key="partitions",
|
key='partitions',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("LVM volumes"),
|
text=tr('LVM volumes'),
|
||||||
action=self._select_lvm_vols,
|
action=self._select_lvm_vols,
|
||||||
value=self._enc_config.lvm_volumes,
|
value=self._enc_config.lvm_volumes,
|
||||||
dependencies=[self._check_dep_lvm_vols],
|
dependencies=[self._check_dep_lvm_vols],
|
||||||
preview_action=self._preview,
|
preview_action=self._preview,
|
||||||
key="lvm_volumes",
|
key='lvm_volumes',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("HSM"),
|
text=tr('HSM'),
|
||||||
action=select_hsm,
|
action=select_hsm,
|
||||||
value=self._enc_config.hsm_device,
|
value=self._enc_config.hsm_device,
|
||||||
dependencies=[self._check_dep_enc_type],
|
dependencies=[self._check_dep_enc_type],
|
||||||
preview_action=self._preview,
|
preview_action=self._preview,
|
||||||
key="hsm_device",
|
key='hsm_device',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -96,19 +96,19 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _check_dep_enc_type(self) -> bool:
|
def _check_dep_enc_type(self) -> bool:
|
||||||
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value
|
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
|
||||||
if enc_type and enc_type != EncryptionType.NoEncryption:
|
if enc_type and enc_type != EncryptionType.NoEncryption:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _check_dep_partitions(self) -> bool:
|
def _check_dep_partitions(self) -> bool:
|
||||||
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value
|
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
|
||||||
if enc_type and enc_type in [EncryptionType.Luks, EncryptionType.LvmOnLuks]:
|
if enc_type and enc_type in [EncryptionType.Luks, EncryptionType.LvmOnLuks]:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _check_dep_lvm_vols(self) -> bool:
|
def _check_dep_lvm_vols(self) -> bool:
|
||||||
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value
|
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
|
||||||
if enc_type and enc_type == EncryptionType.LuksOnLvm:
|
if enc_type and enc_type == EncryptionType.LuksOnLvm:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
@ -117,10 +117,10 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
||||||
def run(self) -> DiskEncryption | None:
|
def run(self) -> DiskEncryption | None:
|
||||||
super().run()
|
super().run()
|
||||||
|
|
||||||
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value
|
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
|
||||||
enc_password: Password | None = self._item_group.find_by_key("encryption_password").value
|
enc_password: Password | None = self._item_group.find_by_key('encryption_password').value
|
||||||
enc_partitions = self._item_group.find_by_key("partitions").value
|
enc_partitions = self._item_group.find_by_key('partitions').value
|
||||||
enc_lvm_vols = self._item_group.find_by_key("lvm_volumes").value
|
enc_lvm_vols = self._item_group.find_by_key('lvm_volumes').value
|
||||||
|
|
||||||
assert enc_type is not None
|
assert enc_type is not None
|
||||||
assert enc_partitions is not None
|
assert enc_partitions is not None
|
||||||
|
|
@ -144,22 +144,22 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _preview(self, item: MenuItem) -> str | None:
|
def _preview(self, item: MenuItem) -> str | None:
|
||||||
output = ""
|
output = ''
|
||||||
|
|
||||||
if (enc_type := self._prev_type()) is not None:
|
if (enc_type := self._prev_type()) is not None:
|
||||||
output += enc_type
|
output += enc_type
|
||||||
|
|
||||||
if (enc_pwd := self._prev_password()) is not None:
|
if (enc_pwd := self._prev_password()) is not None:
|
||||||
output += f"\n{enc_pwd}"
|
output += f'\n{enc_pwd}'
|
||||||
|
|
||||||
if (fido_device := self._prev_hsm()) is not None:
|
if (fido_device := self._prev_hsm()) is not None:
|
||||||
output += f"\n{fido_device}"
|
output += f'\n{fido_device}'
|
||||||
|
|
||||||
if (partitions := self._prev_partitions()) is not None:
|
if (partitions := self._prev_partitions()) is not None:
|
||||||
output += f"\n\n{partitions}"
|
output += f'\n\n{partitions}'
|
||||||
|
|
||||||
if (lvm := self._prev_lvm_vols()) is not None:
|
if (lvm := self._prev_lvm_vols()) is not None:
|
||||||
output += f"\n\n{lvm}"
|
output += f'\n\n{lvm}'
|
||||||
|
|
||||||
if not output:
|
if not output:
|
||||||
return None
|
return None
|
||||||
|
|
@ -167,51 +167,51 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def _prev_type(self) -> str | None:
|
def _prev_type(self) -> str | None:
|
||||||
enc_type = self._item_group.find_by_key("encryption_type").value
|
enc_type = self._item_group.find_by_key('encryption_type').value
|
||||||
|
|
||||||
if enc_type:
|
if enc_type:
|
||||||
enc_text = EncryptionType.type_to_text(enc_type)
|
enc_text = EncryptionType.type_to_text(enc_type)
|
||||||
return f"{tr('Encryption type')}: {enc_text}"
|
return f'{tr("Encryption type")}: {enc_text}'
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_password(self) -> str | None:
|
def _prev_password(self) -> str | None:
|
||||||
enc_pwd = self._item_group.find_by_key("encryption_password").value
|
enc_pwd = self._item_group.find_by_key('encryption_password').value
|
||||||
|
|
||||||
if enc_pwd:
|
if enc_pwd:
|
||||||
return f"{tr('Encryption password')}: {enc_pwd.hidden()}"
|
return f'{tr("Encryption password")}: {enc_pwd.hidden()}'
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_partitions(self) -> str | None:
|
def _prev_partitions(self) -> str | None:
|
||||||
partitions: list[PartitionModification] | None = self._item_group.find_by_key("partitions").value
|
partitions: list[PartitionModification] | None = self._item_group.find_by_key('partitions').value
|
||||||
|
|
||||||
if partitions:
|
if partitions:
|
||||||
output = tr("Partitions to be encrypted") + "\n"
|
output = tr('Partitions to be encrypted') + '\n'
|
||||||
output += FormattedOutput.as_table(partitions)
|
output += FormattedOutput.as_table(partitions)
|
||||||
return output.rstrip()
|
return output.rstrip()
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_lvm_vols(self) -> str | None:
|
def _prev_lvm_vols(self) -> str | None:
|
||||||
volumes: list[PartitionModification] | None = self._item_group.find_by_key("lvm_volumes").value
|
volumes: list[PartitionModification] | None = self._item_group.find_by_key('lvm_volumes').value
|
||||||
|
|
||||||
if volumes:
|
if volumes:
|
||||||
output = tr("LVM volumes to be encrypted") + "\n"
|
output = tr('LVM volumes to be encrypted') + '\n'
|
||||||
output += FormattedOutput.as_table(volumes)
|
output += FormattedOutput.as_table(volumes)
|
||||||
return output.rstrip()
|
return output.rstrip()
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_hsm(self) -> str | None:
|
def _prev_hsm(self) -> str | None:
|
||||||
fido_device: Fido2Device | None = self._item_group.find_by_key("hsm_device").value
|
fido_device: Fido2Device | None = self._item_group.find_by_key('hsm_device').value
|
||||||
|
|
||||||
if not fido_device:
|
if not fido_device:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
output = str(fido_device.path)
|
output = str(fido_device.path)
|
||||||
output += f" ({fido_device.manufacturer}, {fido_device.product})"
|
output += f' ({fido_device.manufacturer}, {fido_device.product})'
|
||||||
return f"{tr('HSM device')}: {output}"
|
return f'{tr("HSM device")}: {output}'
|
||||||
|
|
||||||
|
|
||||||
def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: EncryptionType) -> EncryptionType | None:
|
def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: EncryptionType) -> EncryptionType | None:
|
||||||
|
|
@ -232,7 +232,7 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Encryption type")),
|
frame=FrameProperties.min(tr('Encryption type')),
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
match result.type_:
|
match result.type_:
|
||||||
|
|
@ -245,9 +245,9 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt
|
||||||
|
|
||||||
|
|
||||||
def select_encrypted_password() -> Password | None:
|
def select_encrypted_password() -> Password | None:
|
||||||
header = tr("Enter disk encryption password (leave blank for no encryption)") + "\n"
|
header = tr('Enter disk encryption password (leave blank for no encryption)') + '\n'
|
||||||
password = get_password(
|
password = get_password(
|
||||||
text=tr("Disk encryption password"),
|
text=tr('Disk encryption password'),
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
)
|
)
|
||||||
|
|
@ -256,7 +256,7 @@ def select_encrypted_password() -> Password | None:
|
||||||
|
|
||||||
|
|
||||||
def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
|
def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
|
||||||
header = tr("Select a FIDO2 device to use for HSM") + "\n"
|
header = tr('Select a FIDO2 device to use for HSM') + '\n'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fido_devices = Fido2.get_fido2_devices()
|
fido_devices = Fido2.get_fido2_devices()
|
||||||
|
|
@ -292,7 +292,7 @@ def select_partitions_to_encrypt(
|
||||||
|
|
||||||
# do not allow encrypting the boot partition
|
# do not allow encrypting the boot partition
|
||||||
for mod in modification:
|
for mod in modification:
|
||||||
partitions += [p for p in mod.partitions if p.mountpoint != Path("/boot") and not p.is_swap()]
|
partitions += [p for p in mod.partitions if p.mountpoint != Path('/boot') and not p.is_swap()]
|
||||||
|
|
||||||
# do not allow encrypting existing partitions that are not marked as wipe
|
# do not allow encrypting existing partitions that are not marked as wipe
|
||||||
avail_partitions = [p for p in partitions if not p.exists()]
|
avail_partitions = [p for p in partitions if not p.exists()]
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,10 @@ class Fido2:
|
||||||
# down moving the cursor in the menu
|
# down moving the cursor in the menu
|
||||||
if not cls._loaded or reload:
|
if not cls._loaded or reload:
|
||||||
try:
|
try:
|
||||||
ret = SysCommand("systemd-cryptenroll --fido2-device=list").decode()
|
ret = SysCommand('systemd-cryptenroll --fido2-device=list').decode()
|
||||||
except SysCallError:
|
except SysCallError:
|
||||||
error("fido2 support is most likely not installed")
|
error('fido2 support is most likely not installed')
|
||||||
raise ValueError("HSM devices can not be detected, is libfido2 installed?")
|
raise ValueError('HSM devices can not be detected, is libfido2 installed?')
|
||||||
|
|
||||||
fido_devices = clear_vt100_escape_codes_from_str(ret)
|
fido_devices = clear_vt100_escape_codes_from_str(ret)
|
||||||
|
|
||||||
|
|
@ -51,10 +51,10 @@ class Fido2:
|
||||||
product_pos = 0
|
product_pos = 0
|
||||||
devices = []
|
devices = []
|
||||||
|
|
||||||
for line in fido_devices.split("\r\n"):
|
for line in fido_devices.split('\r\n'):
|
||||||
if "/dev" not in line:
|
if '/dev' not in line:
|
||||||
manufacturer_pos = line.find("MANUFACTURER")
|
manufacturer_pos = line.find('MANUFACTURER')
|
||||||
product_pos = line.find("PRODUCT")
|
product_pos = line.find('PRODUCT')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
path = line[:manufacturer_pos].rstrip()
|
path = line[:manufacturer_pos].rstrip()
|
||||||
|
|
@ -77,18 +77,18 @@ class Fido2:
|
||||||
dev_path: Path,
|
dev_path: Path,
|
||||||
password: Password,
|
password: Password,
|
||||||
) -> None:
|
) -> None:
|
||||||
worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device.path} {dev_path}", peek_output=True)
|
worker = SysCommandWorker(f'systemd-cryptenroll --fido2-device={hsm_device.path} {dev_path}', peek_output=True)
|
||||||
pw_inputted = False
|
pw_inputted = False
|
||||||
pin_inputted = False
|
pin_inputted = False
|
||||||
|
|
||||||
while worker.is_alive():
|
while worker.is_alive():
|
||||||
if pw_inputted is False:
|
if pw_inputted is False:
|
||||||
if bytes(f"please enter current passphrase for disk {dev_path}", "UTF-8") in worker._trace_log.lower():
|
if bytes(f'please enter current passphrase for disk {dev_path}', 'UTF-8') in worker._trace_log.lower():
|
||||||
worker.write(bytes(password.plaintext, "UTF-8"))
|
worker.write(bytes(password.plaintext, 'UTF-8'))
|
||||||
pw_inputted = True
|
pw_inputted = True
|
||||||
elif pin_inputted is False:
|
elif pin_inputted is False:
|
||||||
if bytes("please enter security token pin", "UTF-8") in worker._trace_log.lower():
|
if bytes('please enter security token pin', 'UTF-8') in worker._trace_log.lower():
|
||||||
worker.write(bytes(getpass.getpass(" "), "UTF-8"))
|
worker.write(bytes(getpass.getpass(' '), 'UTF-8'))
|
||||||
pin_inputted = True
|
pin_inputted = True
|
||||||
|
|
||||||
info("You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds")
|
info('You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds')
|
||||||
|
|
|
||||||
|
|
@ -37,16 +37,16 @@ class FilesystemHandler:
|
||||||
|
|
||||||
def perform_filesystem_operations(self, show_countdown: bool = True) -> None:
|
def perform_filesystem_operations(self, show_countdown: bool = True) -> None:
|
||||||
if self._disk_config.config_type == DiskLayoutType.Pre_mount:
|
if self._disk_config.config_type == DiskLayoutType.Pre_mount:
|
||||||
debug("Disk layout configuration is set to pre-mount, not performing any operations")
|
debug('Disk layout configuration is set to pre-mount, not performing any operations')
|
||||||
return
|
return
|
||||||
|
|
||||||
device_mods = [d for d in self._disk_config.device_modifications if d.partitions]
|
device_mods = [d for d in self._disk_config.device_modifications if d.partitions]
|
||||||
|
|
||||||
if not device_mods:
|
if not device_mods:
|
||||||
debug("No modifications required")
|
debug('No modifications required')
|
||||||
return
|
return
|
||||||
|
|
||||||
device_paths = ", ".join([str(mod.device.device_info.path) for mod in device_mods])
|
device_paths = ', '.join([str(mod.device.device_info.path) for mod in device_mods])
|
||||||
|
|
||||||
if show_countdown:
|
if show_countdown:
|
||||||
self._final_warning(device_paths)
|
self._final_warning(device_paths)
|
||||||
|
|
@ -66,7 +66,7 @@ class FilesystemHandler:
|
||||||
if self._disk_config.lvm_config:
|
if self._disk_config.lvm_config:
|
||||||
for mod in device_mods:
|
for mod in device_mods:
|
||||||
if boot_part := mod.get_boot_partition():
|
if boot_part := mod.get_boot_partition():
|
||||||
debug(f"Formatting boot partition: {boot_part.dev_path}")
|
debug(f'Formatting boot partition: {boot_part.dev_path}')
|
||||||
self._format_partitions(
|
self._format_partitions(
|
||||||
[boot_part],
|
[boot_part],
|
||||||
mod.device_path,
|
mod.device_path,
|
||||||
|
|
@ -123,11 +123,11 @@ class FilesystemHandler:
|
||||||
def _validate_partitions(self, partitions: list[PartitionModification]) -> None:
|
def _validate_partitions(self, partitions: list[PartitionModification]) -> None:
|
||||||
checks = {
|
checks = {
|
||||||
# verify that all partitions have a path set (which implies that they have been created)
|
# verify that all partitions have a path set (which implies that they have been created)
|
||||||
lambda x: x.dev_path is None: ValueError("When formatting, all partitions must have a path set"),
|
lambda x: x.dev_path is None: ValueError('When formatting, all partitions must have a path set'),
|
||||||
# crypto luks is not a valid file system type
|
# crypto luks is not a valid file system type
|
||||||
lambda x: x.fs_type is FilesystemType.Crypto_luks: ValueError("Crypto luks cannot be set as a filesystem type"),
|
lambda x: x.fs_type is FilesystemType.Crypto_luks: ValueError('Crypto luks cannot be set as a filesystem type'),
|
||||||
# file system type must be set
|
# file system type must be set
|
||||||
lambda x: x.fs_type is None: ValueError("File system type must be set for modification"),
|
lambda x: x.fs_type is None: ValueError('File system type must be set for modification'),
|
||||||
}
|
}
|
||||||
|
|
||||||
for check, exc in checks.items():
|
for check, exc in checks.items():
|
||||||
|
|
@ -136,7 +136,7 @@ class FilesystemHandler:
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
def perform_lvm_operations(self) -> None:
|
def perform_lvm_operations(self) -> None:
|
||||||
info("Setting up LVM config...")
|
info('Setting up LVM config...')
|
||||||
|
|
||||||
if not self._disk_config.lvm_config:
|
if not self._disk_config.lvm_config:
|
||||||
return
|
return
|
||||||
|
|
@ -195,7 +195,7 @@ class FilesystemHandler:
|
||||||
vg_info = device_handler.lvm_group_info(vg.name)
|
vg_info = device_handler.lvm_group_info(vg.name)
|
||||||
|
|
||||||
if not vg_info:
|
if not vg_info:
|
||||||
raise ValueError("Unable to fetch VG info")
|
raise ValueError('Unable to fetch VG info')
|
||||||
|
|
||||||
# the actual available LVM Group size will be smaller than the
|
# the actual available LVM Group size will be smaller than the
|
||||||
# total PVs size due to reserved metadata storage etc.
|
# total PVs size due to reserved metadata storage etc.
|
||||||
|
|
@ -213,11 +213,11 @@ class FilesystemHandler:
|
||||||
for lv in vg.volumes:
|
for lv in vg.volumes:
|
||||||
offset = max_vol_offset if lv == max_vol else None
|
offset = max_vol_offset if lv == max_vol else None
|
||||||
|
|
||||||
debug(f"vg: {vg.name}, vol: {lv.name}, offset: {offset}")
|
debug(f'vg: {vg.name}, vol: {lv.name}, offset: {offset}')
|
||||||
device_handler.lvm_vol_create(vg.name, lv, offset)
|
device_handler.lvm_vol_create(vg.name, lv, offset)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
debug("Fetching LVM volume info")
|
debug('Fetching LVM volume info')
|
||||||
lv_info = device_handler.lvm_vol_info(lv.name)
|
lv_info = device_handler.lvm_vol_info(lv.name)
|
||||||
if lv_info is not None:
|
if lv_info is not None:
|
||||||
break
|
break
|
||||||
|
|
@ -234,7 +234,7 @@ class FilesystemHandler:
|
||||||
for vol in lvm_config.get_all_volumes():
|
for vol in lvm_config.get_all_volumes():
|
||||||
if enc_vol := enc_vols.get(vol, None):
|
if enc_vol := enc_vols.get(vol, None):
|
||||||
if not enc_vol.mapper_dev:
|
if not enc_vol.mapper_dev:
|
||||||
raise ValueError("No mapper device defined")
|
raise ValueError('No mapper device defined')
|
||||||
path = enc_vol.mapper_dev
|
path = enc_vol.mapper_dev
|
||||||
else:
|
else:
|
||||||
path = vol.safe_dev_path
|
path = vol.safe_dev_path
|
||||||
|
|
@ -340,13 +340,13 @@ class FilesystemHandler:
|
||||||
def _final_warning(self, device_paths: str) -> bool:
|
def _final_warning(self, device_paths: str) -> bool:
|
||||||
# Issue a final warning before we continue with something un-revertable.
|
# Issue a final warning before we continue with something un-revertable.
|
||||||
# We mention the drive one last time, and count from 5 to 0.
|
# We mention the drive one last time, and count from 5 to 0.
|
||||||
out = tr(" ! Formatting {} in ").format(device_paths)
|
out = tr(' ! Formatting {} in ').format(device_paths)
|
||||||
Tui.print(out, row=0, endl="", clear_screen=True)
|
Tui.print(out, row=0, endl='', clear_screen=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
countdown = "\n5...4...3...2...1\n"
|
countdown = '\n5...4...3...2...1\n'
|
||||||
for c in countdown:
|
for c in countdown:
|
||||||
Tui.print(c, row=0, endl="")
|
Tui.print(c, row=0, endl='')
|
||||||
time.sleep(0.25)
|
time.sleep(0.25)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
with Tui():
|
with Tui():
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ class FreeSpace:
|
||||||
Called for displaying data in table format
|
Called for displaying data in table format
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
"Start": self.start.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
|
'Start': self.start.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
|
||||||
"End": self.end.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
|
'End': self.end.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
|
||||||
"Size": self.length.format_highest(),
|
'Size': self.length.format_highest(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ class DiskSegment:
|
||||||
length=self.segment.length,
|
length=self.segment.length,
|
||||||
)
|
)
|
||||||
data = part_mod.table_data()
|
data = part_mod.table_data()
|
||||||
data.update({"Status": "free", "Type": "", "FS type": ""})
|
data.update({'Status': 'free', 'Type': '', 'FS type': ''})
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -85,26 +85,26 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
self._using_gpt = device_mod.using_gpt(partition_table)
|
self._using_gpt = device_mod.using_gpt(partition_table)
|
||||||
|
|
||||||
self._actions = {
|
self._actions = {
|
||||||
"suggest_partition_layout": tr("Suggest partition layout"),
|
'suggest_partition_layout': tr('Suggest partition layout'),
|
||||||
"remove_added_partitions": tr("Remove all newly added partitions"),
|
'remove_added_partitions': tr('Remove all newly added partitions'),
|
||||||
"assign_mountpoint": tr("Assign mountpoint"),
|
'assign_mountpoint': tr('Assign mountpoint'),
|
||||||
"mark_formatting": tr("Mark/Unmark to be formatted (wipes data)"),
|
'mark_formatting': tr('Mark/Unmark to be formatted (wipes data)'),
|
||||||
"mark_bootable": tr("Mark/Unmark as bootable"),
|
'mark_bootable': tr('Mark/Unmark as bootable'),
|
||||||
}
|
}
|
||||||
if self._using_gpt:
|
if self._using_gpt:
|
||||||
self._actions.update(
|
self._actions.update(
|
||||||
{
|
{
|
||||||
"mark_esp": tr("Mark/Unmark as ESP"),
|
'mark_esp': tr('Mark/Unmark as ESP'),
|
||||||
"mark_xbootldr": tr("Mark/Unmark as XBOOTLDR"),
|
'mark_xbootldr': tr('Mark/Unmark as XBOOTLDR'),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self._actions.update(
|
self._actions.update(
|
||||||
{
|
{
|
||||||
"set_filesystem": tr("Change filesystem"),
|
'set_filesystem': tr('Change filesystem'),
|
||||||
"btrfs_mark_compressed": tr("Mark/Unmark as compressed"), # btrfs only
|
'btrfs_mark_compressed': tr('Mark/Unmark as compressed'), # btrfs only
|
||||||
"btrfs_mark_nodatacow": tr("Mark/Unmark as nodatacow"), # btrfs only
|
'btrfs_mark_nodatacow': tr('Mark/Unmark as nodatacow'), # btrfs only
|
||||||
"btrfs_set_subvolumes": tr("Set subvolumes"), # btrfs only
|
'btrfs_set_subvolumes': tr('Set subvolumes'), # btrfs only
|
||||||
"delete_partition": tr("Delete partition"),
|
'delete_partition': tr('Delete partition'),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -119,9 +119,9 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
else:
|
else:
|
||||||
device_partitions = device_mod.partitions
|
device_partitions = device_mod.partitions
|
||||||
|
|
||||||
prompt = tr("Partition management: {}").format(device.device_info.path) + "\n"
|
prompt = tr('Partition management: {}').format(device.device_info.path) + '\n'
|
||||||
prompt += tr("Total length: {}").format(device.device_info.total_size.format_size(Unit.MiB))
|
prompt += tr('Total length: {}').format(device.device_info.total_size.format_size(Unit.MiB))
|
||||||
self._info = prompt + "\n"
|
self._info = prompt + '\n'
|
||||||
|
|
||||||
display_actions = list(self._actions.values())
|
display_actions = list(self._actions.values())
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|
@ -132,7 +132,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
)
|
)
|
||||||
|
|
||||||
def wipe_str(self) -> str:
|
def wipe_str(self) -> str:
|
||||||
return "{}: {}".format(tr("Wipe"), self._wipe)
|
return '{}: {}'.format(tr('Wipe'), self._wipe)
|
||||||
|
|
||||||
def as_segments(self, device_partitions: list[PartitionModification]) -> list[DiskSegment]:
|
def as_segments(self, device_partitions: list[PartitionModification]) -> list[DiskSegment]:
|
||||||
end = self._device.device_info.total_size
|
end = self._device.device_info.total_size
|
||||||
|
|
@ -202,7 +202,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
def _run_actions_on_entry(self, entry: DiskSegment) -> None:
|
def _run_actions_on_entry(self, entry: DiskSegment) -> None:
|
||||||
# Do not create a menu when the segment is free space
|
# Do not create a menu when the segment is free space
|
||||||
if isinstance(entry.segment, FreeSpace):
|
if isinstance(entry.segment, FreeSpace):
|
||||||
self._data = self.handle_action("", entry, self._data)
|
self._data = self.handle_action('', entry, self._data)
|
||||||
else:
|
else:
|
||||||
super()._run_actions_on_entry(entry)
|
super()._run_actions_on_entry(entry)
|
||||||
|
|
||||||
|
|
@ -210,18 +210,18 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
def selected_action_display(self, selection: DiskSegment) -> str:
|
def selected_action_display(self, selection: DiskSegment) -> str:
|
||||||
if isinstance(selection.segment, PartitionModification):
|
if isinstance(selection.segment, PartitionModification):
|
||||||
if selection.segment.status == ModificationStatus.Create:
|
if selection.segment.status == ModificationStatus.Create:
|
||||||
return tr("Partition - New")
|
return tr('Partition - New')
|
||||||
elif selection.segment.is_delete() and selection.segment.dev_path:
|
elif selection.segment.is_delete() and selection.segment.dev_path:
|
||||||
title = tr("Partition") + "\n\n"
|
title = tr('Partition') + '\n\n'
|
||||||
title += "status: delete\n"
|
title += 'status: delete\n'
|
||||||
title += f"device: {selection.segment.dev_path}\n"
|
title += f'device: {selection.segment.dev_path}\n'
|
||||||
for part in self._device.partition_infos:
|
for part in self._device.partition_infos:
|
||||||
if part.path == selection.segment.dev_path:
|
if part.path == selection.segment.dev_path:
|
||||||
if part.partuuid:
|
if part.partuuid:
|
||||||
title += f"partuuid: {part.partuuid}"
|
title += f'partuuid: {part.partuuid}'
|
||||||
return title
|
return title
|
||||||
return str(selection.segment.dev_path)
|
return str(selection.segment.dev_path)
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def filter_options(self, selection: DiskSegment, options: list[str]) -> list[str]:
|
def filter_options(self, selection: DiskSegment, options: list[str]) -> list[str]:
|
||||||
|
|
@ -232,7 +232,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
not_filter = list(self._actions.values())
|
not_filter = list(self._actions.values())
|
||||||
# only display formatting if the partition exists already
|
# only display formatting if the partition exists already
|
||||||
elif not selection.segment.exists():
|
elif not selection.segment.exists():
|
||||||
not_filter += [self._actions["mark_formatting"]]
|
not_filter += [self._actions['mark_formatting']]
|
||||||
else:
|
else:
|
||||||
# only allow options if the existing partition
|
# only allow options if the existing partition
|
||||||
# was marked as formatting, otherwise we run into issues where
|
# was marked as formatting, otherwise we run into issues where
|
||||||
|
|
@ -240,29 +240,29 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
# 2. Switch back to old filesystem -> should unmark wipe now, but
|
# 2. Switch back to old filesystem -> should unmark wipe now, but
|
||||||
# how do we know it was the original one?
|
# how do we know it was the original one?
|
||||||
not_filter += [
|
not_filter += [
|
||||||
self._actions["set_filesystem"],
|
self._actions['set_filesystem'],
|
||||||
self._actions["mark_bootable"],
|
self._actions['mark_bootable'],
|
||||||
]
|
]
|
||||||
if self._using_gpt:
|
if self._using_gpt:
|
||||||
not_filter += [
|
not_filter += [
|
||||||
self._actions["mark_esp"],
|
self._actions['mark_esp'],
|
||||||
self._actions["mark_xbootldr"],
|
self._actions['mark_xbootldr'],
|
||||||
]
|
]
|
||||||
not_filter += [
|
not_filter += [
|
||||||
self._actions["btrfs_mark_compressed"],
|
self._actions['btrfs_mark_compressed'],
|
||||||
self._actions["btrfs_mark_nodatacow"],
|
self._actions['btrfs_mark_nodatacow'],
|
||||||
self._actions["btrfs_set_subvolumes"],
|
self._actions['btrfs_set_subvolumes'],
|
||||||
]
|
]
|
||||||
|
|
||||||
# non btrfs partitions shouldn't get btrfs options
|
# non btrfs partitions shouldn't get btrfs options
|
||||||
if selection.segment.fs_type != FilesystemType.Btrfs:
|
if selection.segment.fs_type != FilesystemType.Btrfs:
|
||||||
not_filter += [
|
not_filter += [
|
||||||
self._actions["btrfs_mark_compressed"],
|
self._actions['btrfs_mark_compressed'],
|
||||||
self._actions["btrfs_mark_nodatacow"],
|
self._actions['btrfs_mark_nodatacow'],
|
||||||
self._actions["btrfs_set_subvolumes"],
|
self._actions['btrfs_set_subvolumes'],
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
not_filter += [self._actions["assign_mountpoint"]]
|
not_filter += [self._actions['assign_mountpoint']]
|
||||||
|
|
||||||
return [o for o in options if o not in not_filter]
|
return [o for o in options if o not in not_filter]
|
||||||
|
|
||||||
|
|
@ -276,21 +276,21 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
if not entry:
|
if not entry:
|
||||||
action_key = [k for k, v in self._actions.items() if v == action][0]
|
action_key = [k for k, v in self._actions.items() if v == action][0]
|
||||||
match action_key:
|
match action_key:
|
||||||
case "suggest_partition_layout":
|
case 'suggest_partition_layout':
|
||||||
part_mods = self.get_part_mods(data)
|
part_mods = self.get_part_mods(data)
|
||||||
device_mod = self._suggest_partition_layout(part_mods)
|
device_mod = self._suggest_partition_layout(part_mods)
|
||||||
if device_mod and device_mod.partitions:
|
if device_mod and device_mod.partitions:
|
||||||
data = self.as_segments(device_mod.partitions)
|
data = self.as_segments(device_mod.partitions)
|
||||||
self._wipe = device_mod.wipe
|
self._wipe = device_mod.wipe
|
||||||
self._prompt = self._info + self.wipe_str()
|
self._prompt = self._info + self.wipe_str()
|
||||||
case "remove_added_partitions":
|
case 'remove_added_partitions':
|
||||||
if self._reset_confirmation():
|
if self._reset_confirmation():
|
||||||
data = [s for s in data if isinstance(s.segment, PartitionModification) and s.segment.is_exists_or_modify()]
|
data = [s for s in data if isinstance(s.segment, PartitionModification) and s.segment.is_exists_or_modify()]
|
||||||
elif isinstance(entry.segment, PartitionModification):
|
elif isinstance(entry.segment, PartitionModification):
|
||||||
partition = entry.segment
|
partition = entry.segment
|
||||||
action_key = [k for k, v in self._actions.items() if v == action][0]
|
action_key = [k for k, v in self._actions.items() if v == action][0]
|
||||||
match action_key:
|
match action_key:
|
||||||
case "assign_mountpoint":
|
case 'assign_mountpoint':
|
||||||
new_mountpoint = self._prompt_mountpoint()
|
new_mountpoint = self._prompt_mountpoint()
|
||||||
if not partition.is_swap():
|
if not partition.is_swap():
|
||||||
if partition.is_home():
|
if partition.is_home():
|
||||||
|
|
@ -306,22 +306,22 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
if partition.is_home():
|
if partition.is_home():
|
||||||
partition.flags = []
|
partition.flags = []
|
||||||
partition.set_flag(PartitionFlag.LINUX_HOME)
|
partition.set_flag(PartitionFlag.LINUX_HOME)
|
||||||
case "mark_formatting":
|
case 'mark_formatting':
|
||||||
self._prompt_formatting(partition)
|
self._prompt_formatting(partition)
|
||||||
case "mark_bootable":
|
case 'mark_bootable':
|
||||||
if not partition.is_swap():
|
if not partition.is_swap():
|
||||||
partition.invert_flag(PartitionFlag.BOOT)
|
partition.invert_flag(PartitionFlag.BOOT)
|
||||||
case "mark_esp":
|
case 'mark_esp':
|
||||||
if not partition.is_root() and not partition.is_home() and not partition.is_swap():
|
if not partition.is_root() and not partition.is_home() and not partition.is_swap():
|
||||||
if PartitionFlag.XBOOTLDR in partition.flags:
|
if PartitionFlag.XBOOTLDR in partition.flags:
|
||||||
partition.invert_flag(PartitionFlag.XBOOTLDR)
|
partition.invert_flag(PartitionFlag.XBOOTLDR)
|
||||||
partition.invert_flag(PartitionFlag.ESP)
|
partition.invert_flag(PartitionFlag.ESP)
|
||||||
case "mark_xbootldr":
|
case 'mark_xbootldr':
|
||||||
if not partition.is_root() and not partition.is_home() and not partition.is_swap():
|
if not partition.is_root() and not partition.is_home() and not partition.is_swap():
|
||||||
if PartitionFlag.ESP in partition.flags:
|
if PartitionFlag.ESP in partition.flags:
|
||||||
partition.invert_flag(PartitionFlag.ESP)
|
partition.invert_flag(PartitionFlag.ESP)
|
||||||
partition.invert_flag(PartitionFlag.XBOOTLDR)
|
partition.invert_flag(PartitionFlag.XBOOTLDR)
|
||||||
case "set_filesystem":
|
case 'set_filesystem':
|
||||||
fs_type = self._prompt_partition_fs_type()
|
fs_type = self._prompt_partition_fs_type()
|
||||||
if fs_type:
|
if fs_type:
|
||||||
if partition.is_swap():
|
if partition.is_swap():
|
||||||
|
|
@ -334,13 +334,13 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
# btrfs subvolumes will define mountpoints
|
# btrfs subvolumes will define mountpoints
|
||||||
if fs_type == FilesystemType.Btrfs:
|
if fs_type == FilesystemType.Btrfs:
|
||||||
partition.mountpoint = None
|
partition.mountpoint = None
|
||||||
case "btrfs_mark_compressed":
|
case 'btrfs_mark_compressed':
|
||||||
self._toggle_mount_option(partition, BtrfsMountOption.compress)
|
self._toggle_mount_option(partition, BtrfsMountOption.compress)
|
||||||
case "btrfs_mark_nodatacow":
|
case 'btrfs_mark_nodatacow':
|
||||||
self._toggle_mount_option(partition, BtrfsMountOption.nodatacow)
|
self._toggle_mount_option(partition, BtrfsMountOption.nodatacow)
|
||||||
case "btrfs_set_subvolumes":
|
case 'btrfs_set_subvolumes':
|
||||||
self._set_btrfs_subvolumes(partition)
|
self._set_btrfs_subvolumes(partition)
|
||||||
case "delete_partition":
|
case 'delete_partition':
|
||||||
data = self._delete_partition(partition, data)
|
data = self._delete_partition(partition, data)
|
||||||
else:
|
else:
|
||||||
part_mods = self.get_part_mods(data)
|
part_mods = self.get_part_mods(data)
|
||||||
|
|
@ -396,7 +396,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
# without asking the user which inner-filesystem they want to use. Since the flag 'encrypted' = True is already set,
|
# without asking the user which inner-filesystem they want to use. Since the flag 'encrypted' = True is already set,
|
||||||
# it's safe to change the filesystem for this partition.
|
# it's safe to change the filesystem for this partition.
|
||||||
if partition.fs_type == FilesystemType.Crypto_luks:
|
if partition.fs_type == FilesystemType.Crypto_luks:
|
||||||
prompt = tr("This partition is currently encrypted, to format it a filesystem has to be specified") + "\n"
|
prompt = tr('This partition is currently encrypted, to format it a filesystem has to be specified') + '\n'
|
||||||
fs_type = self._prompt_partition_fs_type(prompt)
|
fs_type = self._prompt_partition_fs_type(prompt)
|
||||||
partition.fs_type = fs_type
|
partition.fs_type = fs_type
|
||||||
|
|
||||||
|
|
@ -404,8 +404,8 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
partition.mountpoint = None
|
partition.mountpoint = None
|
||||||
|
|
||||||
def _prompt_mountpoint(self) -> Path:
|
def _prompt_mountpoint(self) -> Path:
|
||||||
header = tr("Partition mount-points are relative to inside the installation, the boot would be /boot as an example.") + "\n"
|
header = tr('Partition mount-points are relative to inside the installation, the boot would be /boot as an example.') + '\n'
|
||||||
prompt = tr("Mountpoint")
|
prompt = tr('Mountpoint')
|
||||||
|
|
||||||
mountpoint = prompt_dir(prompt, header, validate=False, allow_skip=False)
|
mountpoint = prompt_dir(prompt, header, validate=False, allow_skip=False)
|
||||||
assert mountpoint
|
assert mountpoint
|
||||||
|
|
@ -421,7 +421,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
group,
|
group,
|
||||||
header=prompt,
|
header=prompt,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Filesystem")),
|
frame=FrameProperties.min(tr('Filesystem')),
|
||||||
allow_skip=False,
|
allow_skip=False,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -429,7 +429,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.get_value()
|
return result.get_value()
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
def _validate_value(
|
def _validate_value(
|
||||||
self,
|
self,
|
||||||
|
|
@ -437,14 +437,14 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
max_size: Size,
|
max_size: Size,
|
||||||
text: str,
|
text: str,
|
||||||
) -> Size | None:
|
) -> Size | None:
|
||||||
match = re.match(r"([0-9]+)([a-zA-Z|%]*)", text, re.I)
|
match = re.match(r'([0-9]+)([a-zA-Z|%]*)', text, re.I)
|
||||||
|
|
||||||
if not match:
|
if not match:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
str_value, unit = match.groups()
|
str_value, unit = match.groups()
|
||||||
|
|
||||||
if unit == "%":
|
if unit == '%':
|
||||||
value = int(max_size.value * (int(str_value) / 100))
|
value = int(max_size.value * (int(str_value) / 100))
|
||||||
unit = max_size.unit.name
|
unit = max_size.unit.name
|
||||||
else:
|
else:
|
||||||
|
|
@ -467,30 +467,30 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
def validate(value: str) -> str | None:
|
def validate(value: str) -> str | None:
|
||||||
size = self._validate_value(sector_size, max_size, value)
|
size = self._validate_value(sector_size, max_size, value)
|
||||||
if not size:
|
if not size:
|
||||||
return tr("Invalid size")
|
return tr('Invalid size')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
device_info = self._device.device_info
|
device_info = self._device.device_info
|
||||||
sector_size = device_info.sector_size
|
sector_size = device_info.sector_size
|
||||||
|
|
||||||
text = tr("Selected free space segment on device {}:").format(device_info.path) + "\n\n"
|
text = tr('Selected free space segment on device {}:').format(device_info.path) + '\n\n'
|
||||||
free_space_table = FormattedOutput.as_table([free_space])
|
free_space_table = FormattedOutput.as_table([free_space])
|
||||||
prompt = text + free_space_table + "\n"
|
prompt = text + free_space_table + '\n'
|
||||||
|
|
||||||
max_sectors = free_space.length.format_size(Unit.sectors, sector_size)
|
max_sectors = free_space.length.format_size(Unit.sectors, sector_size)
|
||||||
max_bytes = free_space.length.format_size(Unit.B)
|
max_bytes = free_space.length.format_size(Unit.B)
|
||||||
|
|
||||||
prompt += tr("Size: {} / {}").format(max_sectors, max_bytes) + "\n\n"
|
prompt += tr('Size: {} / {}').format(max_sectors, max_bytes) + '\n\n'
|
||||||
prompt += tr("All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB...") + "\n"
|
prompt += tr('All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB...') + '\n'
|
||||||
prompt += tr("If no unit is provided, the value is interpreted as sectors") + "\n"
|
prompt += tr('If no unit is provided, the value is interpreted as sectors') + '\n'
|
||||||
|
|
||||||
max_size = free_space.length
|
max_size = free_space.length
|
||||||
|
|
||||||
title = tr("Size (default: {}): ").format(max_size.format_highest())
|
title = tr('Size (default: {}): ').format(max_size.format_highest())
|
||||||
|
|
||||||
result = EditMenu(
|
result = EditMenu(
|
||||||
title,
|
title,
|
||||||
header=f"{prompt}\b",
|
header=f'{prompt}\b',
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
validator=validate,
|
validator=validate,
|
||||||
).input()
|
).input()
|
||||||
|
|
@ -529,7 +529,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
mountpoint=mountpoint,
|
mountpoint=mountpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
if partition.mountpoint == Path("/boot"):
|
if partition.mountpoint == Path('/boot'):
|
||||||
partition.set_flag(PartitionFlag.BOOT)
|
partition.set_flag(PartitionFlag.BOOT)
|
||||||
if self._using_gpt:
|
if self._using_gpt:
|
||||||
partition.set_flag(PartitionFlag.ESP)
|
partition.set_flag(PartitionFlag.ESP)
|
||||||
|
|
@ -541,7 +541,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
||||||
return partition
|
return partition
|
||||||
|
|
||||||
def _reset_confirmation(self) -> bool:
|
def _reset_confirmation(self) -> bool:
|
||||||
prompt = tr("This will remove all newly added partitions, continue?") + "\n"
|
prompt = tr('This will remove all newly added partitions, continue?') + '\n'
|
||||||
|
|
||||||
result = SelectMenu[bool](
|
result = SelectMenu[bool](
|
||||||
MenuItemGroup.yes_no(),
|
MenuItemGroup.yes_no(),
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
|
||||||
prompt: str | None = None,
|
prompt: str | None = None,
|
||||||
):
|
):
|
||||||
self._actions = [
|
self._actions = [
|
||||||
tr("Add subvolume"),
|
tr('Add subvolume'),
|
||||||
tr("Edit subvolume"),
|
tr('Edit subvolume'),
|
||||||
tr("Delete subvolume"),
|
tr('Delete subvolume'),
|
||||||
]
|
]
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|
@ -36,7 +36,7 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
|
||||||
|
|
||||||
def _add_subvolume(self, preset: SubvolumeModification | None = None) -> SubvolumeModification | None:
|
def _add_subvolume(self, preset: SubvolumeModification | None = None) -> SubvolumeModification | None:
|
||||||
result = EditMenu(
|
result = EditMenu(
|
||||||
tr("Subvolume name"),
|
tr('Subvolume name'),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
default_text=str(preset.name) if preset else None,
|
default_text=str(preset.name) if preset else None,
|
||||||
|
|
@ -48,14 +48,14 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
name = result.text()
|
name = result.text()
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
case _:
|
case _:
|
||||||
assert_never(result.type_)
|
assert_never(result.type_)
|
||||||
|
|
||||||
header = f"{tr('Subvolume name')}: {name}\n"
|
header = f'{tr("Subvolume name")}: {name}\n'
|
||||||
|
|
||||||
path = prompt_dir(
|
path = prompt_dir(
|
||||||
tr("Subvolume mountpoint"),
|
tr('Subvolume mountpoint'),
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
validate=False,
|
validate=False,
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,13 @@ def _fetch_lsblk_info(
|
||||||
reverse: bool = False,
|
reverse: bool = False,
|
||||||
full_dev_path: bool = False,
|
full_dev_path: bool = False,
|
||||||
) -> LsblkOutput:
|
) -> LsblkOutput:
|
||||||
cmd = ["lsblk", "--json", "--bytes", "--output", ",".join(LsblkInfo.fields())]
|
cmd = ['lsblk', '--json', '--bytes', '--output', ','.join(LsblkInfo.fields())]
|
||||||
|
|
||||||
if reverse:
|
if reverse:
|
||||||
cmd.append("--inverse")
|
cmd.append('--inverse')
|
||||||
|
|
||||||
if full_dev_path:
|
if full_dev_path:
|
||||||
cmd.append("--paths")
|
cmd.append('--paths')
|
||||||
|
|
||||||
if dev_path:
|
if dev_path:
|
||||||
cmd.append(str(dev_path))
|
cmd.append(str(dev_path))
|
||||||
|
|
@ -33,7 +33,7 @@ def _fetch_lsblk_info(
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
# Get the output minus the message/info from lsblk if it returns a non-zero exit code.
|
# Get the output minus the message/info from lsblk if it returns a non-zero exit code.
|
||||||
if err.worker_log:
|
if err.worker_log:
|
||||||
debug(f"Error calling lsblk: {err.worker_log.decode()}")
|
debug(f'Error calling lsblk: {err.worker_log.decode()}')
|
||||||
|
|
||||||
if dev_path:
|
if dev_path:
|
||||||
raise DiskError(f'Failed to read disk "{dev_path}" with lsblk')
|
raise DiskError(f'Failed to read disk "{dev_path}" with lsblk')
|
||||||
|
|
@ -104,8 +104,8 @@ def disk_layouts() -> str:
|
||||||
try:
|
try:
|
||||||
lsblk_output = get_lsblk_output()
|
lsblk_output = get_lsblk_output()
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
warn(f"Could not return disk layouts: {err}")
|
warn(f'Could not return disk layouts: {err}')
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
return lsblk_output.model_dump_json(indent=4)
|
return lsblk_output.model_dump_json(indent=4)
|
||||||
|
|
||||||
|
|
@ -116,13 +116,13 @@ def umount(mountpoint: Path, recursive: bool = False) -> None:
|
||||||
if not lsblk_info.mountpoints:
|
if not lsblk_info.mountpoints:
|
||||||
return
|
return
|
||||||
|
|
||||||
debug(f"Partition {mountpoint} is currently mounted at: {[str(m) for m in lsblk_info.mountpoints]}")
|
debug(f'Partition {mountpoint} is currently mounted at: {[str(m) for m in lsblk_info.mountpoints]}')
|
||||||
|
|
||||||
cmd = ["umount"]
|
cmd = ['umount']
|
||||||
|
|
||||||
if recursive:
|
if recursive:
|
||||||
cmd.append("-R")
|
cmd.append('-R')
|
||||||
|
|
||||||
for path in lsblk_info.mountpoints:
|
for path in lsblk_info.mountpoints:
|
||||||
debug(f"Unmounting mountpoint: {path}")
|
debug(f'Unmounting mountpoint: {path}')
|
||||||
SysCommand(cmd + [str(path)])
|
SysCommand(cmd + [str(path)])
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class UnknownFilesystemFormat(Exception):
|
||||||
|
|
||||||
|
|
||||||
class SysCallError(Exception):
|
class SysCallError(Exception):
|
||||||
def __init__(self, message: str, exit_code: int | None = None, worker_log: bytes = b"") -> None:
|
def __init__(self, message: str, exit_code: int | None = None, worker_log: bytes = b'') -> None:
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
self.message = message
|
self.message = message
|
||||||
self.exit_code = exit_code
|
self.exit_code = exit_code
|
||||||
|
|
|
||||||
|
|
@ -23,27 +23,27 @@ from .output import debug, error
|
||||||
from .storage import storage
|
from .storage import storage
|
||||||
|
|
||||||
# https://stackoverflow.com/a/43627833/929999
|
# https://stackoverflow.com/a/43627833/929999
|
||||||
_VT100_ESCAPE_REGEX = r"\x1B\[[?0-9;]*[a-zA-Z]"
|
_VT100_ESCAPE_REGEX = r'\x1B\[[?0-9;]*[a-zA-Z]'
|
||||||
_VT100_ESCAPE_REGEX_BYTES = _VT100_ESCAPE_REGEX.encode()
|
_VT100_ESCAPE_REGEX_BYTES = _VT100_ESCAPE_REGEX.encode()
|
||||||
|
|
||||||
|
|
||||||
def generate_password(length: int = 64) -> str:
|
def generate_password(length: int = 64) -> str:
|
||||||
haystack = string.printable # digits, ascii_letters, punctuation (!"#$[] etc) and whitespace
|
haystack = string.printable # digits, ascii_letters, punctuation (!"#$[] etc) and whitespace
|
||||||
return "".join(secrets.choice(haystack) for _ in range(length))
|
return ''.join(secrets.choice(haystack) for _ in range(length))
|
||||||
|
|
||||||
|
|
||||||
def locate_binary(name: str) -> str:
|
def locate_binary(name: str) -> str:
|
||||||
if path := which(name):
|
if path := which(name):
|
||||||
return path
|
return path
|
||||||
raise RequirementError(f"Binary {name} does not exist.")
|
raise RequirementError(f'Binary {name} does not exist.')
|
||||||
|
|
||||||
|
|
||||||
def clear_vt100_escape_codes(data: bytes) -> bytes:
|
def clear_vt100_escape_codes(data: bytes) -> bytes:
|
||||||
return re.sub(_VT100_ESCAPE_REGEX_BYTES, b"", data)
|
return re.sub(_VT100_ESCAPE_REGEX_BYTES, b'', data)
|
||||||
|
|
||||||
|
|
||||||
def clear_vt100_escape_codes_from_str(data: str) -> str:
|
def clear_vt100_escape_codes_from_str(data: str) -> str:
|
||||||
return re.sub(_VT100_ESCAPE_REGEX, "", data)
|
return re.sub(_VT100_ESCAPE_REGEX, '', data)
|
||||||
|
|
||||||
|
|
||||||
def jsonify(obj: object, safe: bool = True) -> object:
|
def jsonify(obj: object, safe: bool = True) -> object:
|
||||||
|
|
@ -57,11 +57,11 @@ def jsonify(obj: object, safe: bool = True) -> object:
|
||||||
return {
|
return {
|
||||||
key: jsonify(value, safe)
|
key: jsonify(value, safe)
|
||||||
for key, value in obj.items()
|
for key, value in obj.items()
|
||||||
if isinstance(key, compatible_types) and not (isinstance(key, str) and key.startswith("!") and safe)
|
if isinstance(key, compatible_types) and not (isinstance(key, str) and key.startswith('!') and safe)
|
||||||
}
|
}
|
||||||
if isinstance(obj, Enum):
|
if isinstance(obj, Enum):
|
||||||
return obj.value
|
return obj.value
|
||||||
if hasattr(obj, "json"):
|
if hasattr(obj, 'json'):
|
||||||
# json() is a friendly name for json-helper, it should return
|
# json() is a friendly name for json-helper, it should return
|
||||||
# a dictionary representation of the object so that it can be
|
# a dictionary representation of the object so that it can be
|
||||||
# processed by the json library.
|
# processed by the json library.
|
||||||
|
|
@ -72,7 +72,7 @@ def jsonify(obj: object, safe: bool = True) -> object:
|
||||||
return [jsonify(item, safe) for item in obj]
|
return [jsonify(item, safe) for item in obj]
|
||||||
if isinstance(obj, Path):
|
if isinstance(obj, Path):
|
||||||
return str(obj)
|
return str(obj)
|
||||||
if hasattr(obj, "__dict__"):
|
if hasattr(obj, '__dict__'):
|
||||||
return vars(obj)
|
return vars(obj)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
@ -104,26 +104,26 @@ class SysCommandWorker:
|
||||||
cmd: str | list[str],
|
cmd: str | list[str],
|
||||||
peek_output: bool | None = False,
|
peek_output: bool | None = False,
|
||||||
environment_vars: dict[str, str] | None = None,
|
environment_vars: dict[str, str] | None = None,
|
||||||
working_directory: str | None = "./",
|
working_directory: str | None = './',
|
||||||
remove_vt100_escape_codes_from_lines: bool = True,
|
remove_vt100_escape_codes_from_lines: bool = True,
|
||||||
):
|
):
|
||||||
if isinstance(cmd, str):
|
if isinstance(cmd, str):
|
||||||
cmd = shlex.split(cmd)
|
cmd = shlex.split(cmd)
|
||||||
|
|
||||||
if cmd and not cmd[0].startswith(("/", "./")): # Path() does not work well
|
if cmd and not cmd[0].startswith(('/', './')): # Path() does not work well
|
||||||
cmd[0] = locate_binary(cmd[0])
|
cmd[0] = locate_binary(cmd[0])
|
||||||
|
|
||||||
self.cmd = cmd
|
self.cmd = cmd
|
||||||
self.peek_output = peek_output
|
self.peek_output = peek_output
|
||||||
# define the standard locale for command outputs. For now the C ascii one. Can be overridden
|
# define the standard locale for command outputs. For now the C ascii one. Can be overridden
|
||||||
self.environment_vars = {"LC_ALL": "C"}
|
self.environment_vars = {'LC_ALL': 'C'}
|
||||||
if environment_vars:
|
if environment_vars:
|
||||||
self.environment_vars.update(environment_vars)
|
self.environment_vars.update(environment_vars)
|
||||||
|
|
||||||
self.working_directory = working_directory
|
self.working_directory = working_directory
|
||||||
|
|
||||||
self.exit_code: int | None = None
|
self.exit_code: int | None = None
|
||||||
self._trace_log = b""
|
self._trace_log = b''
|
||||||
self._trace_log_pos = 0
|
self._trace_log_pos = 0
|
||||||
self.poll_object = epoll()
|
self.poll_object = epoll()
|
||||||
self.child_fd: int | None = None
|
self.child_fd: int | None = None
|
||||||
|
|
@ -146,13 +146,13 @@ class SysCommandWorker:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]:
|
def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]:
|
||||||
last_line = self._trace_log.rfind(b"\n")
|
last_line = self._trace_log.rfind(b'\n')
|
||||||
lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines())
|
lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines())
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if self.remove_vt100_escape_codes_from_lines:
|
if self.remove_vt100_escape_codes_from_lines:
|
||||||
line = clear_vt100_escape_codes(line)
|
line = clear_vt100_escape_codes(line)
|
||||||
|
|
||||||
yield line + b"\n"
|
yield line + b'\n'
|
||||||
|
|
||||||
self._trace_log_pos = last_line
|
self._trace_log_pos = last_line
|
||||||
|
|
||||||
|
|
@ -164,11 +164,11 @@ class SysCommandWorker:
|
||||||
@override
|
@override
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
try:
|
try:
|
||||||
return self._trace_log.decode("utf-8")
|
return self._trace_log.decode('utf-8')
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
return str(self._trace_log)
|
return str(self._trace_log)
|
||||||
|
|
||||||
def __enter__(self) -> "SysCommandWorker":
|
def __enter__(self) -> 'SysCommandWorker':
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *args: str) -> None:
|
def __exit__(self, *args: str) -> None:
|
||||||
|
|
@ -184,7 +184,7 @@ class SysCommandWorker:
|
||||||
if self.peek_output:
|
if self.peek_output:
|
||||||
# To make sure any peaked output didn't leave us hanging
|
# To make sure any peaked output didn't leave us hanging
|
||||||
# on the same line we were on.
|
# on the same line we were on.
|
||||||
sys.stdout.write("\n")
|
sys.stdout.write('\n')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
if len(args) >= 2 and args[1]:
|
if len(args) >= 2 and args[1]:
|
||||||
|
|
@ -192,7 +192,7 @@ class SysCommandWorker:
|
||||||
|
|
||||||
if self.exit_code != 0:
|
if self.exit_code != 0:
|
||||||
raise SysCallError(
|
raise SysCallError(
|
||||||
f"{self.cmd} exited with abnormal exit code [{self.exit_code}]: {str(self)[-500:]}",
|
f'{self.cmd} exited with abnormal exit code [{self.exit_code}]: {str(self)[-500:]}',
|
||||||
self.exit_code,
|
self.exit_code,
|
||||||
worker_log=self._trace_log,
|
worker_log=self._trace_log,
|
||||||
)
|
)
|
||||||
|
|
@ -211,7 +211,7 @@ class SysCommandWorker:
|
||||||
self.make_sure_we_are_executing()
|
self.make_sure_we_are_executing()
|
||||||
|
|
||||||
if self.child_fd:
|
if self.child_fd:
|
||||||
return os.write(self.child_fd, data + (b"\n" if line_ending else b""))
|
return os.write(self.child_fd, data + (b'\n' if line_ending else b''))
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
@ -233,17 +233,17 @@ class SysCommandWorker:
|
||||||
if self.peek_output:
|
if self.peek_output:
|
||||||
if isinstance(output, bytes):
|
if isinstance(output, bytes):
|
||||||
try:
|
try:
|
||||||
output = output.decode("UTF-8")
|
output = output.decode('UTF-8')
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
peak_logfile = Path(f"{storage['LOG_PATH']}/cmd_output.txt")
|
peak_logfile = Path(f'{storage["LOG_PATH"]}/cmd_output.txt')
|
||||||
|
|
||||||
change_perm = False
|
change_perm = False
|
||||||
if peak_logfile.exists() is False:
|
if peak_logfile.exists() is False:
|
||||||
change_perm = True
|
change_perm = True
|
||||||
|
|
||||||
with peak_logfile.open("a") as peek_output_log:
|
with peak_logfile.open('a') as peek_output_log:
|
||||||
peek_output_log.write(str(output))
|
peek_output_log.write(str(output))
|
||||||
|
|
||||||
if change_perm:
|
if change_perm:
|
||||||
|
|
@ -301,7 +301,7 @@ class SysCommandWorker:
|
||||||
try:
|
try:
|
||||||
os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars})
|
os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars})
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
error(f"{self.cmd[0]} does not exist.")
|
error(f'{self.cmd[0]} does not exist.')
|
||||||
self.exit_code = 1
|
self.exit_code = 1
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|
@ -313,7 +313,7 @@ class SysCommandWorker:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def decode(self, encoding: str = "UTF-8") -> str:
|
def decode(self, encoding: str = 'UTF-8') -> str:
|
||||||
return self._trace_log.decode(encoding)
|
return self._trace_log.decode(encoding)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -323,7 +323,7 @@ class SysCommand:
|
||||||
cmd: str | list[str],
|
cmd: str | list[str],
|
||||||
peek_output: bool | None = False,
|
peek_output: bool | None = False,
|
||||||
environment_vars: dict[str, str] | None = None,
|
environment_vars: dict[str, str] | None = None,
|
||||||
working_directory: str | None = "./",
|
working_directory: str | None = './',
|
||||||
remove_vt100_escape_codes_from_lines: bool = True,
|
remove_vt100_escape_codes_from_lines: bool = True,
|
||||||
):
|
):
|
||||||
self.cmd = cmd
|
self.cmd = cmd
|
||||||
|
|
@ -351,7 +351,7 @@ class SysCommand:
|
||||||
|
|
||||||
def __getitem__(self, key: slice) -> bytes | None:
|
def __getitem__(self, key: slice) -> bytes | None:
|
||||||
if not self.session:
|
if not self.session:
|
||||||
raise KeyError("SysCommand() does not have an active session.")
|
raise KeyError('SysCommand() does not have an active session.')
|
||||||
elif type(key) is slice:
|
elif type(key) is slice:
|
||||||
start = key.start or 0
|
start = key.start or 0
|
||||||
end = key.stop or len(self.session._trace_log)
|
end = key.stop or len(self.session._trace_log)
|
||||||
|
|
@ -362,7 +362,7 @@ class SysCommand:
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def __repr__(self, *args: list[Any], **kwargs: dict[str, Any]) -> str:
|
def __repr__(self, *args: list[Any], **kwargs: dict[str, Any]) -> str:
|
||||||
return self.decode("UTF-8", errors="backslashreplace") or ""
|
return self.decode('UTF-8', errors='backslashreplace') or ''
|
||||||
|
|
||||||
def create_session(self) -> bool:
|
def create_session(self) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
@ -386,14 +386,14 @@ class SysCommand:
|
||||||
self.session.poll()
|
self.session.poll()
|
||||||
|
|
||||||
if self.peek_output:
|
if self.peek_output:
|
||||||
sys.stdout.write("\n")
|
sys.stdout.write('\n')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def decode(self, encoding: str = "utf-8", errors: str = "backslashreplace", strip: bool = True) -> str:
|
def decode(self, encoding: str = 'utf-8', errors: str = 'backslashreplace', strip: bool = True) -> str:
|
||||||
if not self.session:
|
if not self.session:
|
||||||
raise ValueError("No session available to decode")
|
raise ValueError('No session available to decode')
|
||||||
|
|
||||||
val = self.session._trace_log.decode(encoding, errors=errors)
|
val = self.session._trace_log.decode(encoding, errors=errors)
|
||||||
|
|
||||||
|
|
@ -403,10 +403,10 @@ class SysCommand:
|
||||||
|
|
||||||
def output(self, remove_cr: bool = True) -> bytes:
|
def output(self, remove_cr: bool = True) -> bytes:
|
||||||
if not self.session:
|
if not self.session:
|
||||||
raise ValueError("No session available")
|
raise ValueError('No session available')
|
||||||
|
|
||||||
if remove_cr:
|
if remove_cr:
|
||||||
return self.session._trace_log.replace(b"\r\n", b"\n")
|
return self.session._trace_log.replace(b'\r\n', b'\n')
|
||||||
|
|
||||||
return self.session._trace_log
|
return self.session._trace_log
|
||||||
|
|
||||||
|
|
@ -425,15 +425,15 @@ class SysCommand:
|
||||||
|
|
||||||
|
|
||||||
def _log_cmd(cmd: list[str]) -> None:
|
def _log_cmd(cmd: list[str]) -> None:
|
||||||
history_logfile = Path(f"{storage['LOG_PATH']}/cmd_history.txt")
|
history_logfile = Path(f'{storage["LOG_PATH"]}/cmd_history.txt')
|
||||||
|
|
||||||
change_perm = False
|
change_perm = False
|
||||||
if history_logfile.exists() is False:
|
if history_logfile.exists() is False:
|
||||||
change_perm = True
|
change_perm = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with history_logfile.open("a") as cmd_log:
|
with history_logfile.open('a') as cmd_log:
|
||||||
cmd_log.write(f"{time.time()} {cmd}\n")
|
cmd_log.write(f'{time.time()} {cmd}\n')
|
||||||
|
|
||||||
if change_perm:
|
if change_perm:
|
||||||
history_logfile.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
history_logfile.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||||
|
|
@ -459,6 +459,6 @@ def run(
|
||||||
|
|
||||||
def _pid_exists(pid: int) -> bool:
|
def _pid_exists(pid: int) -> bool:
|
||||||
try:
|
try:
|
||||||
return any(subprocess.check_output(["ps", "--no-headers", "-o", "pid", "-p", str(pid)]).strip())
|
return any(subprocess.check_output(['ps', '--no-headers', '-o', 'pid', '-p', str(pid)]).strip())
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -55,151 +55,151 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
def _get_menu_options(self) -> list[MenuItem]:
|
def _get_menu_options(self) -> list[MenuItem]:
|
||||||
return [
|
return [
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Archinstall language"),
|
text=tr('Archinstall language'),
|
||||||
action=self._select_archinstall_language,
|
action=self._select_archinstall_language,
|
||||||
display_action=lambda x: x.display_name if x else "",
|
display_action=lambda x: x.display_name if x else '',
|
||||||
key="archinstall_language",
|
key='archinstall_language',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Locales"),
|
text=tr('Locales'),
|
||||||
action=self._locale_selection,
|
action=self._locale_selection,
|
||||||
preview_action=self._prev_locale,
|
preview_action=self._prev_locale,
|
||||||
key="locale_config",
|
key='locale_config',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Mirrors and repositories"),
|
text=tr('Mirrors and repositories'),
|
||||||
action=self._mirror_configuration,
|
action=self._mirror_configuration,
|
||||||
preview_action=self._prev_mirror_config,
|
preview_action=self._prev_mirror_config,
|
||||||
key="mirror_config",
|
key='mirror_config',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Disk configuration"),
|
text=tr('Disk configuration'),
|
||||||
action=self._select_disk_config,
|
action=self._select_disk_config,
|
||||||
preview_action=self._prev_disk_config,
|
preview_action=self._prev_disk_config,
|
||||||
mandatory=True,
|
mandatory=True,
|
||||||
key="disk_config",
|
key='disk_config',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Disk encryption"),
|
text=tr('Disk encryption'),
|
||||||
action=self._disk_encryption,
|
action=self._disk_encryption,
|
||||||
preview_action=self._prev_disk_encryption,
|
preview_action=self._prev_disk_encryption,
|
||||||
dependencies=["disk_config"],
|
dependencies=['disk_config'],
|
||||||
key="disk_encryption",
|
key='disk_encryption',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Swap"),
|
text=tr('Swap'),
|
||||||
value=True,
|
value=True,
|
||||||
action=ask_for_swap,
|
action=ask_for_swap,
|
||||||
preview_action=self._prev_swap,
|
preview_action=self._prev_swap,
|
||||||
key="swap",
|
key='swap',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Bootloader"),
|
text=tr('Bootloader'),
|
||||||
value=Bootloader.get_default(),
|
value=Bootloader.get_default(),
|
||||||
action=self._select_bootloader,
|
action=self._select_bootloader,
|
||||||
preview_action=self._prev_bootloader,
|
preview_action=self._prev_bootloader,
|
||||||
mandatory=True,
|
mandatory=True,
|
||||||
key="bootloader",
|
key='bootloader',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Unified kernel images"),
|
text=tr('Unified kernel images'),
|
||||||
value=False,
|
value=False,
|
||||||
enabled=SysInfo.has_uefi(),
|
enabled=SysInfo.has_uefi(),
|
||||||
action=ask_for_uki,
|
action=ask_for_uki,
|
||||||
preview_action=self._prev_uki,
|
preview_action=self._prev_uki,
|
||||||
key="uki",
|
key='uki',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Hostname"),
|
text=tr('Hostname'),
|
||||||
value="archlinux",
|
value='archlinux',
|
||||||
action=ask_hostname,
|
action=ask_hostname,
|
||||||
preview_action=self._prev_hostname,
|
preview_action=self._prev_hostname,
|
||||||
key="hostname",
|
key='hostname',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Root password"),
|
text=tr('Root password'),
|
||||||
action=self._set_root_password,
|
action=self._set_root_password,
|
||||||
preview_action=self._prev_root_pwd,
|
preview_action=self._prev_root_pwd,
|
||||||
key="root_enc_password",
|
key='root_enc_password',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("User account"),
|
text=tr('User account'),
|
||||||
action=self._create_user_account,
|
action=self._create_user_account,
|
||||||
preview_action=self._prev_users,
|
preview_action=self._prev_users,
|
||||||
key="users",
|
key='users',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Profile"),
|
text=tr('Profile'),
|
||||||
action=self._select_profile,
|
action=self._select_profile,
|
||||||
preview_action=self._prev_profile,
|
preview_action=self._prev_profile,
|
||||||
key="profile_config",
|
key='profile_config',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Audio"),
|
text=tr('Audio'),
|
||||||
action=ask_for_audio_selection,
|
action=ask_for_audio_selection,
|
||||||
preview_action=self._prev_audio,
|
preview_action=self._prev_audio,
|
||||||
key="audio_config",
|
key='audio_config',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Kernels"),
|
text=tr('Kernels'),
|
||||||
value=["linux"],
|
value=['linux'],
|
||||||
action=select_kernel,
|
action=select_kernel,
|
||||||
preview_action=self._prev_kernel,
|
preview_action=self._prev_kernel,
|
||||||
mandatory=True,
|
mandatory=True,
|
||||||
key="kernels",
|
key='kernels',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Network configuration"),
|
text=tr('Network configuration'),
|
||||||
action=ask_to_configure_network,
|
action=ask_to_configure_network,
|
||||||
value={},
|
value={},
|
||||||
preview_action=self._prev_network_config,
|
preview_action=self._prev_network_config,
|
||||||
key="network_config",
|
key='network_config',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Parallel Downloads"),
|
text=tr('Parallel Downloads'),
|
||||||
action=add_number_of_parallel_downloads,
|
action=add_number_of_parallel_downloads,
|
||||||
value=0,
|
value=0,
|
||||||
preview_action=self._prev_parallel_dw,
|
preview_action=self._prev_parallel_dw,
|
||||||
key="parallel_downloads",
|
key='parallel_downloads',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Additional packages"),
|
text=tr('Additional packages'),
|
||||||
action=self._select_additional_packages,
|
action=self._select_additional_packages,
|
||||||
value=[],
|
value=[],
|
||||||
preview_action=self._prev_additional_pkgs,
|
preview_action=self._prev_additional_pkgs,
|
||||||
key="packages",
|
key='packages',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Timezone"),
|
text=tr('Timezone'),
|
||||||
action=ask_for_a_timezone,
|
action=ask_for_a_timezone,
|
||||||
value="UTC",
|
value='UTC',
|
||||||
preview_action=self._prev_tz,
|
preview_action=self._prev_tz,
|
||||||
key="timezone",
|
key='timezone',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Automatic time sync (NTP)"),
|
text=tr('Automatic time sync (NTP)'),
|
||||||
action=ask_ntp,
|
action=ask_ntp,
|
||||||
value=True,
|
value=True,
|
||||||
preview_action=self._prev_ntp,
|
preview_action=self._prev_ntp,
|
||||||
key="ntp",
|
key='ntp',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text="",
|
text='',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Save configuration"),
|
text=tr('Save configuration'),
|
||||||
action=lambda x: self._safe_config(),
|
action=lambda x: self._safe_config(),
|
||||||
key=f"{CONFIG_KEY}_save",
|
key=f'{CONFIG_KEY}_save',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Install"),
|
text=tr('Install'),
|
||||||
preview_action=self._prev_install_invalid_config,
|
preview_action=self._prev_install_invalid_config,
|
||||||
key=f"{CONFIG_KEY}_install",
|
key=f'{CONFIG_KEY}_install',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Abort"),
|
text=tr('Abort'),
|
||||||
action=lambda x: exit(1),
|
action=lambda x: exit(1),
|
||||||
key=f"{CONFIG_KEY}_abort",
|
key=f'{CONFIG_KEY}_abort',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -218,7 +218,7 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
return item.has_value()
|
return item.has_value()
|
||||||
|
|
||||||
def has_superuser() -> bool:
|
def has_superuser() -> bool:
|
||||||
item = self._item_group.find_by_key("users")
|
item = self._item_group.find_by_key('users')
|
||||||
|
|
||||||
if item.has_value():
|
if item.has_value():
|
||||||
users = item.value
|
users = item.value
|
||||||
|
|
@ -229,10 +229,10 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
missing = set()
|
missing = set()
|
||||||
|
|
||||||
for item in self._item_group.items:
|
for item in self._item_group.items:
|
||||||
if item.key in ["root_enc_password", "users"]:
|
if item.key in ['root_enc_password', 'users']:
|
||||||
if not check("root_enc_password") and not has_superuser():
|
if not check('root_enc_password') and not has_superuser():
|
||||||
missing.add(
|
missing.add(
|
||||||
tr("Either root-password or at least 1 user with sudo privileges must be specified"),
|
tr('Either root-password or at least 1 user with sudo privileges must be specified'),
|
||||||
)
|
)
|
||||||
elif item.mandatory:
|
elif item.mandatory:
|
||||||
if not check(item.key):
|
if not check(item.key):
|
||||||
|
|
@ -271,11 +271,11 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
self._item_group.find_by_key(o.key).text = o.text
|
self._item_group.find_by_key(o.key).text = o.text
|
||||||
|
|
||||||
def _disk_encryption(self, preset: DiskEncryption | None) -> DiskEncryption | None:
|
def _disk_encryption(self, preset: DiskEncryption | None) -> DiskEncryption | None:
|
||||||
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key("disk_config").value
|
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
|
||||||
|
|
||||||
if not disk_config:
|
if not disk_config:
|
||||||
# this should not happen as the encryption menu has the disk_config as dependency
|
# this should not happen as the encryption menu has the disk_config as dependency
|
||||||
raise ValueError("No disk layout specified")
|
raise ValueError('No disk layout specified')
|
||||||
|
|
||||||
if not DiskEncryption.validate_enc(disk_config):
|
if not DiskEncryption.validate_enc(disk_config):
|
||||||
return None
|
return None
|
||||||
|
|
@ -300,26 +300,26 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
if network_config.type == NicType.MANUAL:
|
if network_config.type == NicType.MANUAL:
|
||||||
output = FormattedOutput.as_table(network_config.nics)
|
output = FormattedOutput.as_table(network_config.nics)
|
||||||
else:
|
else:
|
||||||
output = f"{tr('Network configuration')}:\n{network_config.type.display_msg()}"
|
output = f'{tr("Network configuration")}:\n{network_config.type.display_msg()}'
|
||||||
|
|
||||||
return output
|
return output
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_additional_pkgs(self, item: MenuItem) -> str | None:
|
def _prev_additional_pkgs(self, item: MenuItem) -> str | None:
|
||||||
if item.value:
|
if item.value:
|
||||||
output = "\n".join(sorted(item.value))
|
output = '\n'.join(sorted(item.value))
|
||||||
return output
|
return output
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_tz(self, item: MenuItem) -> str | None:
|
def _prev_tz(self, item: MenuItem) -> str | None:
|
||||||
if item.value:
|
if item.value:
|
||||||
return f"{tr('Timezone')}: {item.value}"
|
return f'{tr("Timezone")}: {item.value}'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_ntp(self, item: MenuItem) -> str | None:
|
def _prev_ntp(self, item: MenuItem) -> str | None:
|
||||||
if item.value is not None:
|
if item.value is not None:
|
||||||
output = f"{tr('NTP')}: "
|
output = f'{tr("NTP")}: '
|
||||||
output += tr("Enabled") if item.value else tr("Disabled")
|
output += tr('Enabled') if item.value else tr('Disabled')
|
||||||
return output
|
return output
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -327,13 +327,13 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
disk_layout_conf: DiskLayoutConfiguration | None = item.value
|
disk_layout_conf: DiskLayoutConfiguration | None = item.value
|
||||||
|
|
||||||
if disk_layout_conf:
|
if disk_layout_conf:
|
||||||
output = tr("Configuration type: {}").format(disk_layout_conf.config_type.display_msg()) + "\n"
|
output = tr('Configuration type: {}').format(disk_layout_conf.config_type.display_msg()) + '\n'
|
||||||
|
|
||||||
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
|
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
|
||||||
output += tr("Mountpoint") + ": " + str(disk_layout_conf.mountpoint)
|
output += tr('Mountpoint') + ': ' + str(disk_layout_conf.mountpoint)
|
||||||
|
|
||||||
if disk_layout_conf.lvm_config:
|
if disk_layout_conf.lvm_config:
|
||||||
output += "{}: {}".format(tr("LVM configuration type"), disk_layout_conf.lvm_config.config_type.display_msg())
|
output += '{}: {}'.format(tr('LVM configuration type'), disk_layout_conf.lvm_config.config_type.display_msg())
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
@ -341,72 +341,72 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
|
|
||||||
def _prev_swap(self, item: MenuItem) -> str | None:
|
def _prev_swap(self, item: MenuItem) -> str | None:
|
||||||
if item.value is not None:
|
if item.value is not None:
|
||||||
output = f"{tr('Swap on zram')}: "
|
output = f'{tr("Swap on zram")}: '
|
||||||
output += tr("Enabled") if item.value else tr("Disabled")
|
output += tr('Enabled') if item.value else tr('Disabled')
|
||||||
return output
|
return output
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_uki(self, item: MenuItem) -> str | None:
|
def _prev_uki(self, item: MenuItem) -> str | None:
|
||||||
if item.value is not None:
|
if item.value is not None:
|
||||||
output = f"{tr('Unified kernel images')}: "
|
output = f'{tr("Unified kernel images")}: '
|
||||||
output += tr("Enabled") if item.value else tr("Disabled")
|
output += tr('Enabled') if item.value else tr('Disabled')
|
||||||
return output
|
return output
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_hostname(self, item: MenuItem) -> str | None:
|
def _prev_hostname(self, item: MenuItem) -> str | None:
|
||||||
if item.value is not None:
|
if item.value is not None:
|
||||||
return f"{tr('Hostname')}: {item.value}"
|
return f'{tr("Hostname")}: {item.value}'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_root_pwd(self, item: MenuItem) -> str | None:
|
def _prev_root_pwd(self, item: MenuItem) -> str | None:
|
||||||
if item.value is not None:
|
if item.value is not None:
|
||||||
password: Password = item.value
|
password: Password = item.value
|
||||||
return f"{tr('Root password')}: {password.hidden()}"
|
return f'{tr("Root password")}: {password.hidden()}'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_audio(self, item: MenuItem) -> str | None:
|
def _prev_audio(self, item: MenuItem) -> str | None:
|
||||||
if item.value is not None:
|
if item.value is not None:
|
||||||
config: AudioConfiguration = item.value
|
config: AudioConfiguration = item.value
|
||||||
return f"{tr('Audio')}: {config.audio.value}"
|
return f'{tr("Audio")}: {config.audio.value}'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_parallel_dw(self, item: MenuItem) -> str | None:
|
def _prev_parallel_dw(self, item: MenuItem) -> str | None:
|
||||||
if item.value is not None:
|
if item.value is not None:
|
||||||
return f"{tr('Parallel Downloads')}: {item.value}"
|
return f'{tr("Parallel Downloads")}: {item.value}'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_kernel(self, item: MenuItem) -> str | None:
|
def _prev_kernel(self, item: MenuItem) -> str | None:
|
||||||
if item.value:
|
if item.value:
|
||||||
kernel = ", ".join(item.value)
|
kernel = ', '.join(item.value)
|
||||||
return f"{tr('Kernel')}: {kernel}"
|
return f'{tr("Kernel")}: {kernel}'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_bootloader(self, item: MenuItem) -> str | None:
|
def _prev_bootloader(self, item: MenuItem) -> str | None:
|
||||||
if item.value is not None:
|
if item.value is not None:
|
||||||
return f"{tr('Bootloader')}: {item.value.value}"
|
return f'{tr("Bootloader")}: {item.value.value}'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_disk_encryption(self, item: MenuItem) -> str | None:
|
def _prev_disk_encryption(self, item: MenuItem) -> str | None:
|
||||||
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key("disk_config").value
|
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
|
||||||
enc_config: DiskEncryption | None = item.value
|
enc_config: DiskEncryption | None = item.value
|
||||||
|
|
||||||
if disk_config and not DiskEncryption.validate_enc(disk_config):
|
if disk_config and not DiskEncryption.validate_enc(disk_config):
|
||||||
return tr("LVM disk encryption with more than 2 partitions is currently not supported")
|
return tr('LVM disk encryption with more than 2 partitions is currently not supported')
|
||||||
|
|
||||||
if enc_config:
|
if enc_config:
|
||||||
enc_type = EncryptionType.type_to_text(enc_config.encryption_type)
|
enc_type = EncryptionType.type_to_text(enc_config.encryption_type)
|
||||||
output = tr("Encryption type") + f": {enc_type}\n"
|
output = tr('Encryption type') + f': {enc_type}\n'
|
||||||
|
|
||||||
if enc_config.encryption_password:
|
if enc_config.encryption_password:
|
||||||
output += tr("Password") + f": {enc_config.encryption_password.hidden()}\n"
|
output += tr('Password') + f': {enc_config.encryption_password.hidden()}\n'
|
||||||
|
|
||||||
if enc_config.partitions:
|
if enc_config.partitions:
|
||||||
output += f"Partitions: {len(enc_config.partitions)} selected\n"
|
output += f'Partitions: {len(enc_config.partitions)} selected\n'
|
||||||
elif enc_config.lvm_volumes:
|
elif enc_config.lvm_volumes:
|
||||||
output += f"LVM volumes: {len(enc_config.lvm_volumes)} selected\n"
|
output += f'LVM volumes: {len(enc_config.lvm_volumes)} selected\n'
|
||||||
|
|
||||||
if enc_config.hsm_device:
|
if enc_config.hsm_device:
|
||||||
output += f"HSM: {enc_config.hsm_device.manufacturer}"
|
output += f'HSM: {enc_config.hsm_device.manufacturer}'
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
@ -423,12 +423,12 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
XXX: The caller is responsible for wrapping the string with the translation
|
XXX: The caller is responsible for wrapping the string with the translation
|
||||||
shim if necessary.
|
shim if necessary.
|
||||||
"""
|
"""
|
||||||
bootloader = self._item_group.find_by_key("bootloader").value
|
bootloader = self._item_group.find_by_key('bootloader').value
|
||||||
root_partition: PartitionModification | None = None
|
root_partition: PartitionModification | None = None
|
||||||
boot_partition: PartitionModification | None = None
|
boot_partition: PartitionModification | None = None
|
||||||
efi_partition: PartitionModification | None = None
|
efi_partition: PartitionModification | None = None
|
||||||
|
|
||||||
if disk_config := self._item_group.find_by_key("disk_config").value:
|
if disk_config := self._item_group.find_by_key('disk_config').value:
|
||||||
for layout in disk_config.device_modifications:
|
for layout in disk_config.device_modifications:
|
||||||
if root_partition := layout.get_root_partition():
|
if root_partition := layout.get_root_partition():
|
||||||
break
|
break
|
||||||
|
|
@ -440,36 +440,36 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
if efi_partition := layout.get_efi_partition():
|
if efi_partition := layout.get_efi_partition():
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
return "No disk layout selected"
|
return 'No disk layout selected'
|
||||||
|
|
||||||
if root_partition is None:
|
if root_partition is None:
|
||||||
return "Root partition not found"
|
return 'Root partition not found'
|
||||||
|
|
||||||
if boot_partition is None:
|
if boot_partition is None:
|
||||||
return "Boot partition not found"
|
return 'Boot partition not found'
|
||||||
|
|
||||||
if SysInfo.has_uefi():
|
if SysInfo.has_uefi():
|
||||||
if efi_partition is None:
|
if efi_partition is None:
|
||||||
return "EFI system partition (ESP) not found"
|
return 'EFI system partition (ESP) not found'
|
||||||
|
|
||||||
if efi_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]:
|
if efi_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]:
|
||||||
return "ESP must be formatted as a FAT filesystem"
|
return 'ESP must be formatted as a FAT filesystem'
|
||||||
|
|
||||||
if bootloader == Bootloader.Limine:
|
if bootloader == Bootloader.Limine:
|
||||||
if boot_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]:
|
if boot_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]:
|
||||||
return "Limine does not support booting with a non-FAT boot partition"
|
return 'Limine does not support booting with a non-FAT boot partition'
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_install_invalid_config(self, item: MenuItem) -> str | None:
|
def _prev_install_invalid_config(self, item: MenuItem) -> str | None:
|
||||||
if missing := self._missing_configs():
|
if missing := self._missing_configs():
|
||||||
text = tr("Missing configurations:\n")
|
text = tr('Missing configurations:\n')
|
||||||
for m in missing:
|
for m in missing:
|
||||||
text += f"- {m}\n"
|
text += f'- {m}\n'
|
||||||
return text[:-1] # remove last new line
|
return text[:-1] # remove last new line
|
||||||
|
|
||||||
if error := self._validate_bootloader():
|
if error := self._validate_bootloader():
|
||||||
return tr(f"Invalid configuration: {error}")
|
return tr(f'Invalid configuration: {error}')
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -484,24 +484,24 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
profile_config: ProfileConfiguration | None = item.value
|
profile_config: ProfileConfiguration | None = item.value
|
||||||
|
|
||||||
if profile_config and profile_config.profile:
|
if profile_config and profile_config.profile:
|
||||||
output = tr("Profiles") + ": "
|
output = tr('Profiles') + ': '
|
||||||
if profile_names := profile_config.profile.current_selection_names():
|
if profile_names := profile_config.profile.current_selection_names():
|
||||||
output += ", ".join(profile_names) + "\n"
|
output += ', '.join(profile_names) + '\n'
|
||||||
else:
|
else:
|
||||||
output += profile_config.profile.name + "\n"
|
output += profile_config.profile.name + '\n'
|
||||||
|
|
||||||
if profile_config.gfx_driver:
|
if profile_config.gfx_driver:
|
||||||
output += tr("Graphics driver") + ": " + profile_config.gfx_driver.value + "\n"
|
output += tr('Graphics driver') + ': ' + profile_config.gfx_driver.value + '\n'
|
||||||
|
|
||||||
if profile_config.greeter:
|
if profile_config.greeter:
|
||||||
output += tr("Greeter") + ": " + profile_config.greeter.value + "\n"
|
output += tr('Greeter') + ': ' + profile_config.greeter.value + '\n'
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _set_root_password(self, preset: str | None = None) -> Password | None:
|
def _set_root_password(self, preset: str | None = None) -> Password | None:
|
||||||
password = get_password(text=tr("Root password"), allow_skip=True)
|
password = get_password(text=tr('Root password'), allow_skip=True)
|
||||||
return password
|
return password
|
||||||
|
|
||||||
def _select_disk_config(
|
def _select_disk_config(
|
||||||
|
|
@ -511,7 +511,7 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
disk_config = DiskLayoutConfigurationMenu(preset).run()
|
disk_config = DiskLayoutConfigurationMenu(preset).run()
|
||||||
|
|
||||||
if disk_config != preset:
|
if disk_config != preset:
|
||||||
self._menu_item_group.find_by_key("disk_encryption").value = None
|
self._menu_item_group.find_by_key('disk_encryption').value = None
|
||||||
|
|
||||||
return disk_config
|
return disk_config
|
||||||
|
|
||||||
|
|
@ -519,7 +519,7 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
bootloader = ask_for_bootloader(preset)
|
bootloader = ask_for_bootloader(preset)
|
||||||
|
|
||||||
if bootloader:
|
if bootloader:
|
||||||
uki = self._item_group.find_by_key("uki")
|
uki = self._item_group.find_by_key('uki')
|
||||||
if not SysInfo.has_uefi() or not bootloader.has_uki_support():
|
if not SysInfo.has_uefi() or not bootloader.has_uki_support():
|
||||||
uki.value = False
|
uki.value = False
|
||||||
uki.enabled = False
|
uki.enabled = False
|
||||||
|
|
@ -535,7 +535,7 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
return profile_config
|
return profile_config
|
||||||
|
|
||||||
def _select_additional_packages(self, preset: list[str]) -> list[str]:
|
def _select_additional_packages(self, preset: list[str]) -> list[str]:
|
||||||
config: MirrorConfiguration | None = self._item_group.find_by_key("mirror_config").value
|
config: MirrorConfiguration | None = self._item_group.find_by_key('mirror_config').value
|
||||||
|
|
||||||
repositories: set[Repository] = set()
|
repositories: set[Repository] = set()
|
||||||
if config:
|
if config:
|
||||||
|
|
@ -574,28 +574,28 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
|
|
||||||
mirror_config: MirrorConfiguration = item.value
|
mirror_config: MirrorConfiguration = item.value
|
||||||
|
|
||||||
output = ""
|
output = ''
|
||||||
if mirror_config.mirror_regions:
|
if mirror_config.mirror_regions:
|
||||||
title = tr("Selected mirror regions")
|
title = tr('Selected mirror regions')
|
||||||
divider = "-" * len(title)
|
divider = '-' * len(title)
|
||||||
regions = mirror_config.region_names
|
regions = mirror_config.region_names
|
||||||
output += f"{title}\n{divider}\n{regions}\n\n"
|
output += f'{title}\n{divider}\n{regions}\n\n'
|
||||||
|
|
||||||
if mirror_config.custom_servers:
|
if mirror_config.custom_servers:
|
||||||
title = tr("Custom servers")
|
title = tr('Custom servers')
|
||||||
divider = "-" * len(title)
|
divider = '-' * len(title)
|
||||||
servers = mirror_config.custom_server_urls
|
servers = mirror_config.custom_server_urls
|
||||||
output += f"{title}\n{divider}\n{servers}\n\n"
|
output += f'{title}\n{divider}\n{servers}\n\n'
|
||||||
|
|
||||||
if mirror_config.optional_repositories:
|
if mirror_config.optional_repositories:
|
||||||
title = tr("Optional repositories")
|
title = tr('Optional repositories')
|
||||||
divider = "-" * len(title)
|
divider = '-' * len(title)
|
||||||
repos = ", ".join([r.value for r in mirror_config.optional_repositories])
|
repos = ', '.join([r.value for r in mirror_config.optional_repositories])
|
||||||
output += f"{title}\n{divider}\n{repos}\n\n"
|
output += f'{title}\n{divider}\n{repos}\n\n'
|
||||||
|
|
||||||
if mirror_config.custom_repositories:
|
if mirror_config.custom_repositories:
|
||||||
title = tr("Custom repositories")
|
title = tr('Custom repositories')
|
||||||
table = FormattedOutput.as_table(mirror_config.custom_repositories)
|
table = FormattedOutput.as_table(mirror_config.custom_repositories)
|
||||||
output += f"{title}:\n\n{table}"
|
output += f'{title}:\n\n{table}'
|
||||||
|
|
||||||
return output.strip()
|
return output.strip()
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ from .translationhandler import tr
|
||||||
|
|
||||||
|
|
||||||
class CpuVendor(Enum):
|
class CpuVendor(Enum):
|
||||||
AuthenticAMD = "amd"
|
AuthenticAMD = 'amd'
|
||||||
GenuineIntel = "intel"
|
GenuineIntel = 'intel'
|
||||||
_Unknown = "unknown"
|
_Unknown = 'unknown'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_vendor(cls, name: str) -> "CpuVendor":
|
def get_vendor(cls, name: str) -> 'CpuVendor':
|
||||||
if vendor := getattr(cls, name, None):
|
if vendor := getattr(cls, name, None):
|
||||||
return vendor
|
return vendor
|
||||||
else:
|
else:
|
||||||
|
|
@ -32,38 +32,38 @@ class CpuVendor(Enum):
|
||||||
|
|
||||||
def get_ucode(self) -> Path | None:
|
def get_ucode(self) -> Path | None:
|
||||||
if self._has_microcode():
|
if self._has_microcode():
|
||||||
return Path(self.value + "-ucode.img")
|
return Path(self.value + '-ucode.img')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class GfxPackage(Enum):
|
class GfxPackage(Enum):
|
||||||
Dkms = "dkms"
|
Dkms = 'dkms'
|
||||||
IntelMediaDriver = "intel-media-driver"
|
IntelMediaDriver = 'intel-media-driver'
|
||||||
LibvaIntelDriver = "libva-intel-driver"
|
LibvaIntelDriver = 'libva-intel-driver'
|
||||||
LibvaMesaDriver = "libva-mesa-driver"
|
LibvaMesaDriver = 'libva-mesa-driver'
|
||||||
LibvaNvidiaDriver = "libva-nvidia-driver"
|
LibvaNvidiaDriver = 'libva-nvidia-driver'
|
||||||
Mesa = "mesa"
|
Mesa = 'mesa'
|
||||||
NvidiaDkms = "nvidia-dkms"
|
NvidiaDkms = 'nvidia-dkms'
|
||||||
NvidiaOpenDkms = "nvidia-open-dkms"
|
NvidiaOpenDkms = 'nvidia-open-dkms'
|
||||||
VulkanIntel = "vulkan-intel"
|
VulkanIntel = 'vulkan-intel'
|
||||||
VulkanRadeon = "vulkan-radeon"
|
VulkanRadeon = 'vulkan-radeon'
|
||||||
VulkanNouveau = "vulkan-nouveau"
|
VulkanNouveau = 'vulkan-nouveau'
|
||||||
Xf86VideoAmdgpu = "xf86-video-amdgpu"
|
Xf86VideoAmdgpu = 'xf86-video-amdgpu'
|
||||||
Xf86VideoAti = "xf86-video-ati"
|
Xf86VideoAti = 'xf86-video-ati'
|
||||||
Xf86VideoNouveau = "xf86-video-nouveau"
|
Xf86VideoNouveau = 'xf86-video-nouveau'
|
||||||
Xf86VideoVmware = "xf86-video-vmware"
|
Xf86VideoVmware = 'xf86-video-vmware'
|
||||||
XorgServer = "xorg-server"
|
XorgServer = 'xorg-server'
|
||||||
XorgXinit = "xorg-xinit"
|
XorgXinit = 'xorg-xinit'
|
||||||
|
|
||||||
|
|
||||||
class GfxDriver(Enum):
|
class GfxDriver(Enum):
|
||||||
AllOpenSource = "All open-source"
|
AllOpenSource = 'All open-source'
|
||||||
AmdOpenSource = "AMD / ATI (open-source)"
|
AmdOpenSource = 'AMD / ATI (open-source)'
|
||||||
IntelOpenSource = "Intel (open-source)"
|
IntelOpenSource = 'Intel (open-source)'
|
||||||
NvidiaOpenKernel = "Nvidia (open kernel module for newer GPUs, Turing+)"
|
NvidiaOpenKernel = 'Nvidia (open kernel module for newer GPUs, Turing+)'
|
||||||
NvidiaOpenSource = "Nvidia (open-source nouveau driver)"
|
NvidiaOpenSource = 'Nvidia (open-source nouveau driver)'
|
||||||
NvidiaProprietary = "Nvidia (proprietary)"
|
NvidiaProprietary = 'Nvidia (proprietary)'
|
||||||
VMOpenSource = "VMware / VirtualBox (open-source)"
|
VMOpenSource = 'VMware / VirtualBox (open-source)'
|
||||||
|
|
||||||
def is_nvidia(self) -> bool:
|
def is_nvidia(self) -> bool:
|
||||||
match self:
|
match self:
|
||||||
|
|
@ -74,10 +74,10 @@ class GfxDriver(Enum):
|
||||||
|
|
||||||
def packages_text(self) -> str:
|
def packages_text(self) -> str:
|
||||||
pkg_names = [p.value for p in self.gfx_packages()]
|
pkg_names = [p.value for p in self.gfx_packages()]
|
||||||
text = tr("Installed packages") + ":\n"
|
text = tr('Installed packages') + ':\n'
|
||||||
|
|
||||||
for p in sorted(pkg_names):
|
for p in sorted(pkg_names):
|
||||||
text += f"\t- {p}\n"
|
text += f'\t- {p}\n'
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
@ -151,13 +151,13 @@ class _SysInfo:
|
||||||
"""
|
"""
|
||||||
Returns system cpu information
|
Returns system cpu information
|
||||||
"""
|
"""
|
||||||
cpu_info_path = Path("/proc/cpuinfo")
|
cpu_info_path = Path('/proc/cpuinfo')
|
||||||
cpu: dict[str, str] = {}
|
cpu: dict[str, str] = {}
|
||||||
|
|
||||||
with cpu_info_path.open() as file:
|
with cpu_info_path.open() as file:
|
||||||
for line in file:
|
for line in file:
|
||||||
if line := line.strip():
|
if line := line.strip():
|
||||||
key, value = line.split(":", maxsplit=1)
|
key, value = line.split(':', maxsplit=1)
|
||||||
cpu[key.strip()] = value.strip()
|
cpu[key.strip()] = value.strip()
|
||||||
|
|
||||||
return cpu
|
return cpu
|
||||||
|
|
@ -167,12 +167,12 @@ class _SysInfo:
|
||||||
"""
|
"""
|
||||||
Returns system memory information
|
Returns system memory information
|
||||||
"""
|
"""
|
||||||
mem_info_path = Path("/proc/meminfo")
|
mem_info_path = Path('/proc/meminfo')
|
||||||
mem_info: dict[str, int] = {}
|
mem_info: dict[str, int] = {}
|
||||||
|
|
||||||
with mem_info_path.open() as file:
|
with mem_info_path.open() as file:
|
||||||
for line in file:
|
for line in file:
|
||||||
key, value = line.strip().split(":")
|
key, value = line.strip().split(':')
|
||||||
num = value.split()[0]
|
num = value.split()[0]
|
||||||
mem_info[key] = int(num)
|
mem_info[key] = int(num)
|
||||||
|
|
||||||
|
|
@ -186,7 +186,7 @@ class _SysInfo:
|
||||||
"""
|
"""
|
||||||
Returns loaded kernel modules
|
Returns loaded kernel modules
|
||||||
"""
|
"""
|
||||||
modules_path = Path("/proc/modules")
|
modules_path = Path('/proc/modules')
|
||||||
modules: list[str] = []
|
modules: list[str] = []
|
||||||
|
|
||||||
with modules_path.open() as file:
|
with modules_path.open() as file:
|
||||||
|
|
@ -204,113 +204,113 @@ class SysInfo:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_wifi() -> bool:
|
def has_wifi() -> bool:
|
||||||
ifaces = list(list_interfaces().values())
|
ifaces = list(list_interfaces().values())
|
||||||
return "WIRELESS" in enrich_iface_types(ifaces).values()
|
return 'WIRELESS' in enrich_iface_types(ifaces).values()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_uefi() -> bool:
|
def has_uefi() -> bool:
|
||||||
return os.path.isdir("/sys/firmware/efi")
|
return os.path.isdir('/sys/firmware/efi')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _graphics_devices() -> dict[str, str]:
|
def _graphics_devices() -> dict[str, str]:
|
||||||
cards: dict[str, str] = {}
|
cards: dict[str, str] = {}
|
||||||
for line in SysCommand("lspci"):
|
for line in SysCommand('lspci'):
|
||||||
if b" VGA " in line or b" 3D " in line:
|
if b' VGA ' in line or b' 3D ' in line:
|
||||||
_, identifier = line.split(b": ", 1)
|
_, identifier = line.split(b': ', 1)
|
||||||
cards[identifier.strip().decode("UTF-8")] = str(line)
|
cards[identifier.strip().decode('UTF-8')] = str(line)
|
||||||
return cards
|
return cards
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_nvidia_graphics() -> bool:
|
def has_nvidia_graphics() -> bool:
|
||||||
return any("nvidia" in x.lower() for x in SysInfo._graphics_devices())
|
return any('nvidia' in x.lower() for x in SysInfo._graphics_devices())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_amd_graphics() -> bool:
|
def has_amd_graphics() -> bool:
|
||||||
return any("amd" in x.lower() for x in SysInfo._graphics_devices())
|
return any('amd' in x.lower() for x in SysInfo._graphics_devices())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_intel_graphics() -> bool:
|
def has_intel_graphics() -> bool:
|
||||||
return any("intel" in x.lower() for x in SysInfo._graphics_devices())
|
return any('intel' in x.lower() for x in SysInfo._graphics_devices())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cpu_vendor() -> CpuVendor | None:
|
def cpu_vendor() -> CpuVendor | None:
|
||||||
if vendor := _sys_info.cpu_info.get("vendor_id"):
|
if vendor := _sys_info.cpu_info.get('vendor_id'):
|
||||||
return CpuVendor.get_vendor(vendor)
|
return CpuVendor.get_vendor(vendor)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cpu_model() -> str | None:
|
def cpu_model() -> str | None:
|
||||||
return _sys_info.cpu_info.get("model name", None)
|
return _sys_info.cpu_info.get('model name', None)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sys_vendor() -> str:
|
def sys_vendor() -> str:
|
||||||
with open("/sys/devices/virtual/dmi/id/sys_vendor") as vendor:
|
with open('/sys/devices/virtual/dmi/id/sys_vendor') as vendor:
|
||||||
return vendor.read().strip()
|
return vendor.read().strip()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def product_name() -> str:
|
def product_name() -> str:
|
||||||
with open("/sys/devices/virtual/dmi/id/product_name") as product:
|
with open('/sys/devices/virtual/dmi/id/product_name') as product:
|
||||||
return product.read().strip()
|
return product.read().strip()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mem_available() -> int:
|
def mem_available() -> int:
|
||||||
return _sys_info.mem_info_by_key("MemAvailable")
|
return _sys_info.mem_info_by_key('MemAvailable')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mem_free() -> int:
|
def mem_free() -> int:
|
||||||
return _sys_info.mem_info_by_key("MemFree")
|
return _sys_info.mem_info_by_key('MemFree')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mem_total() -> int:
|
def mem_total() -> int:
|
||||||
return _sys_info.mem_info_by_key("MemTotal")
|
return _sys_info.mem_info_by_key('MemTotal')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def virtualization() -> str | None:
|
def virtualization() -> str | None:
|
||||||
try:
|
try:
|
||||||
return str(SysCommand("systemd-detect-virt")).strip("\r\n")
|
return str(SysCommand('systemd-detect-virt')).strip('\r\n')
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
debug(f"Could not detect virtual system: {err}")
|
debug(f'Could not detect virtual system: {err}')
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_vm() -> bool:
|
def is_vm() -> bool:
|
||||||
try:
|
try:
|
||||||
result = SysCommand("systemd-detect-virt")
|
result = SysCommand('systemd-detect-virt')
|
||||||
return b"none" not in b"".join(result).lower()
|
return b'none' not in b''.join(result).lower()
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
debug(f"System is not running in a VM: {err}")
|
debug(f'System is not running in a VM: {err}')
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def requires_sof_fw() -> bool:
|
def requires_sof_fw() -> bool:
|
||||||
return "snd_sof" in _sys_info.loaded_modules
|
return 'snd_sof' in _sys_info.loaded_modules
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def requires_alsa_fw() -> bool:
|
def requires_alsa_fw() -> bool:
|
||||||
modules = (
|
modules = (
|
||||||
"snd_asihpi",
|
'snd_asihpi',
|
||||||
"snd_cs46xx",
|
'snd_cs46xx',
|
||||||
"snd_darla20",
|
'snd_darla20',
|
||||||
"snd_darla24",
|
'snd_darla24',
|
||||||
"snd_echo3g",
|
'snd_echo3g',
|
||||||
"snd_emu10k1",
|
'snd_emu10k1',
|
||||||
"snd_gina20",
|
'snd_gina20',
|
||||||
"snd_gina24",
|
'snd_gina24',
|
||||||
"snd_hda_codec_ca0132",
|
'snd_hda_codec_ca0132',
|
||||||
"snd_hdsp",
|
'snd_hdsp',
|
||||||
"snd_indigo",
|
'snd_indigo',
|
||||||
"snd_indigodj",
|
'snd_indigodj',
|
||||||
"snd_indigodjx",
|
'snd_indigodjx',
|
||||||
"snd_indigoio",
|
'snd_indigoio',
|
||||||
"snd_indigoiox",
|
'snd_indigoiox',
|
||||||
"snd_layla20",
|
'snd_layla20',
|
||||||
"snd_layla24",
|
'snd_layla24',
|
||||||
"snd_mia",
|
'snd_mia',
|
||||||
"snd_mixart",
|
'snd_mixart',
|
||||||
"snd_mona",
|
'snd_mona',
|
||||||
"snd_pcxhr",
|
'snd_pcxhr',
|
||||||
"snd_vx_lib",
|
'snd_vx_lib',
|
||||||
)
|
)
|
||||||
|
|
||||||
for loaded_module in _sys_info.loaded_modules:
|
for loaded_module in _sys_info.loaded_modules:
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -20,26 +20,26 @@ from .network_menu import ManualNetworkConfig, ask_to_configure_network
|
||||||
from .system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_driver, select_kernel
|
from .system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_driver, select_kernel
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"ManualNetworkConfig",
|
'ManualNetworkConfig',
|
||||||
"UserList",
|
'UserList',
|
||||||
"add_number_of_parallel_downloads",
|
'add_number_of_parallel_downloads',
|
||||||
"ask_additional_packages_to_install",
|
'ask_additional_packages_to_install',
|
||||||
"ask_for_a_timezone",
|
'ask_for_a_timezone',
|
||||||
"ask_for_additional_users",
|
'ask_for_additional_users',
|
||||||
"ask_for_audio_selection",
|
'ask_for_audio_selection',
|
||||||
"ask_for_bootloader",
|
'ask_for_bootloader',
|
||||||
"ask_for_swap",
|
'ask_for_swap',
|
||||||
"ask_for_uki",
|
'ask_for_uki',
|
||||||
"ask_hostname",
|
'ask_hostname',
|
||||||
"ask_ntp",
|
'ask_ntp',
|
||||||
"ask_to_configure_network",
|
'ask_to_configure_network',
|
||||||
"get_default_partition_layout",
|
'get_default_partition_layout',
|
||||||
"select_archinstall_language",
|
'select_archinstall_language',
|
||||||
"select_devices",
|
'select_devices',
|
||||||
"select_disk_config",
|
'select_disk_config',
|
||||||
"select_driver",
|
'select_driver',
|
||||||
"select_kernel",
|
'select_kernel',
|
||||||
"select_main_filesystem_format",
|
'select_main_filesystem_format',
|
||||||
"suggest_multi_disk_layout",
|
'suggest_multi_disk_layout',
|
||||||
"suggest_single_disk_layout",
|
'suggest_single_disk_layout',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ def select_devices(preset: list[BDevice] | None = []) -> list[BDevice]:
|
||||||
search_enabled=False,
|
search_enabled=False,
|
||||||
multi=True,
|
multi=True,
|
||||||
preview_style=PreviewStyle.BOTTOM,
|
preview_style=PreviewStyle.BOTTOM,
|
||||||
preview_size="auto",
|
preview_size='auto',
|
||||||
preview_frame=FrameProperties.max("Partitions"),
|
preview_frame=FrameProperties.max('Partitions'),
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -136,7 +136,7 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
|
||||||
group,
|
group,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Disk configuration type")),
|
frame=FrameProperties.min(tr('Disk configuration type')),
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -149,10 +149,10 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
|
||||||
selection = result.get_value()
|
selection = result.get_value()
|
||||||
|
|
||||||
if selection == pre_mount_mode:
|
if selection == pre_mount_mode:
|
||||||
output = "You will use whatever drive-setup is mounted at the specified directory\n"
|
output = 'You will use whatever drive-setup is mounted at the specified directory\n'
|
||||||
output += "WARNING: Archinstall won't check the suitability of this setup\n"
|
output += "WARNING: Archinstall won't check the suitability of this setup\n"
|
||||||
|
|
||||||
path = prompt_dir(tr("Root mount directory"), output, allow_skip=True)
|
path = prompt_dir(tr('Root mount directory'), output, allow_skip=True)
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
return None
|
return None
|
||||||
|
|
@ -206,7 +206,7 @@ def select_lvm_config(
|
||||||
group,
|
group,
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
frame=FrameProperties.min(tr("LVM configuration type")),
|
frame=FrameProperties.min(tr('LVM configuration type')),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -235,7 +235,7 @@ def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModifi
|
||||||
type=PartitionType.Primary,
|
type=PartitionType.Primary,
|
||||||
start=start,
|
start=start,
|
||||||
length=size,
|
length=size,
|
||||||
mountpoint=Path("/boot"),
|
mountpoint=Path('/boot'),
|
||||||
fs_type=FilesystemType.Fat32,
|
fs_type=FilesystemType.Fat32,
|
||||||
flags=flags,
|
flags=flags,
|
||||||
)
|
)
|
||||||
|
|
@ -243,20 +243,20 @@ def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModifi
|
||||||
|
|
||||||
def select_main_filesystem_format() -> FilesystemType:
|
def select_main_filesystem_format() -> FilesystemType:
|
||||||
items = [
|
items = [
|
||||||
MenuItem("btrfs", value=FilesystemType.Btrfs),
|
MenuItem('btrfs', value=FilesystemType.Btrfs),
|
||||||
MenuItem("ext4", value=FilesystemType.Ext4),
|
MenuItem('ext4', value=FilesystemType.Ext4),
|
||||||
MenuItem("xfs", value=FilesystemType.Xfs),
|
MenuItem('xfs', value=FilesystemType.Xfs),
|
||||||
MenuItem("f2fs", value=FilesystemType.F2fs),
|
MenuItem('f2fs', value=FilesystemType.F2fs),
|
||||||
]
|
]
|
||||||
|
|
||||||
if arch_config_handler.args.advanced:
|
if arch_config_handler.args.advanced:
|
||||||
items.append(MenuItem("ntfs", value=FilesystemType.Ntfs))
|
items.append(MenuItem('ntfs', value=FilesystemType.Ntfs))
|
||||||
|
|
||||||
group = MenuItemGroup(items, sort_items=False)
|
group = MenuItemGroup(items, sort_items=False)
|
||||||
result = SelectMenu[FilesystemType](
|
result = SelectMenu[FilesystemType](
|
||||||
group,
|
group,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min("Filesystem"),
|
frame=FrameProperties.min('Filesystem'),
|
||||||
allow_skip=False,
|
allow_skip=False,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -264,13 +264,13 @@ def select_main_filesystem_format() -> FilesystemType:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.get_value()
|
return result.get_value()
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
|
|
||||||
def select_mount_options() -> list[str]:
|
def select_mount_options() -> list[str]:
|
||||||
prompt = tr("Would you like to use compression or disable CoW?") + "\n"
|
prompt = tr('Would you like to use compression or disable CoW?') + '\n'
|
||||||
compression = tr("Use compression")
|
compression = tr('Use compression')
|
||||||
disable_cow = tr("Disable Copy-on-Write")
|
disable_cow = tr('Disable Copy-on-Write')
|
||||||
|
|
||||||
items = [
|
items = [
|
||||||
MenuItem(compression, value=BtrfsMountOption.compress.value),
|
MenuItem(compression, value=BtrfsMountOption.compress.value),
|
||||||
|
|
@ -293,7 +293,7 @@ def select_mount_options() -> list[str]:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return [result.get_value()]
|
return [result.get_value()]
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
|
|
||||||
def process_root_partition_size(total_size: Size, sector_size: SectorSize) -> Size:
|
def process_root_partition_size(total_size: Size, sector_size: SectorSize) -> Size:
|
||||||
|
|
@ -325,7 +325,7 @@ def suggest_single_disk_layout(
|
||||||
min_size_to_allow_home_part = Size(64, Unit.GiB, sector_size)
|
min_size_to_allow_home_part = Size(64, Unit.GiB, sector_size)
|
||||||
|
|
||||||
if filesystem_type == FilesystemType.Btrfs:
|
if filesystem_type == FilesystemType.Btrfs:
|
||||||
prompt = tr("Would you like to use BTRFS subvolumes with a default structure?") + "\n"
|
prompt = tr('Would you like to use BTRFS subvolumes with a default structure?') + '\n'
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
group.set_focus_by_value(MenuItem.yes().value)
|
group.set_focus_by_value(MenuItem.yes().value)
|
||||||
result = SelectMenu[bool](
|
result = SelectMenu[bool](
|
||||||
|
|
@ -362,7 +362,7 @@ def suggest_single_disk_layout(
|
||||||
elif separate_home:
|
elif separate_home:
|
||||||
using_home_partition = True
|
using_home_partition = True
|
||||||
else:
|
else:
|
||||||
prompt = tr("Would you like to create a separate partition for /home?") + "\n"
|
prompt = tr('Would you like to create a separate partition for /home?') + '\n'
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
group.set_focus_by_value(MenuItem.yes().value)
|
group.set_focus_by_value(MenuItem.yes().value)
|
||||||
result = SelectMenu(
|
result = SelectMenu(
|
||||||
|
|
@ -390,7 +390,7 @@ def suggest_single_disk_layout(
|
||||||
type=PartitionType.Primary,
|
type=PartitionType.Primary,
|
||||||
start=root_start,
|
start=root_start,
|
||||||
length=root_length,
|
length=root_length,
|
||||||
mountpoint=Path("/") if not using_subvolumes else None,
|
mountpoint=Path('/') if not using_subvolumes else None,
|
||||||
fs_type=filesystem_type,
|
fs_type=filesystem_type,
|
||||||
mount_options=mount_options,
|
mount_options=mount_options,
|
||||||
)
|
)
|
||||||
|
|
@ -402,10 +402,10 @@ def suggest_single_disk_layout(
|
||||||
# https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash
|
# https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash
|
||||||
# https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh
|
# https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh
|
||||||
subvolumes = [
|
subvolumes = [
|
||||||
SubvolumeModification(Path("@"), Path("/")),
|
SubvolumeModification(Path('@'), Path('/')),
|
||||||
SubvolumeModification(Path("@home"), Path("/home")),
|
SubvolumeModification(Path('@home'), Path('/home')),
|
||||||
SubvolumeModification(Path("@log"), Path("/var/log")),
|
SubvolumeModification(Path('@log'), Path('/var/log')),
|
||||||
SubvolumeModification(Path("@pkg"), Path("/var/cache/pacman/pkg")),
|
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
|
||||||
]
|
]
|
||||||
root_partition.btrfs_subvols = subvolumes
|
root_partition.btrfs_subvols = subvolumes
|
||||||
elif using_home_partition:
|
elif using_home_partition:
|
||||||
|
|
@ -424,7 +424,7 @@ def suggest_single_disk_layout(
|
||||||
type=PartitionType.Primary,
|
type=PartitionType.Primary,
|
||||||
start=home_start,
|
start=home_start,
|
||||||
length=home_length,
|
length=home_length,
|
||||||
mountpoint=Path("/home"),
|
mountpoint=Path('/home'),
|
||||||
fs_type=filesystem_type,
|
fs_type=filesystem_type,
|
||||||
mount_options=mount_options,
|
mount_options=mount_options,
|
||||||
flags=flags,
|
flags=flags,
|
||||||
|
|
@ -467,11 +467,11 @@ def suggest_multi_disk_layout(
|
||||||
root_device: BDevice | None = sorted_delta[0][0]
|
root_device: BDevice | None = sorted_delta[0][0]
|
||||||
|
|
||||||
if home_device is None or root_device is None:
|
if home_device is None or root_device is None:
|
||||||
text = tr("The selected drives do not have the minimum capacity required for an automatic suggestion\n")
|
text = tr('The selected drives do not have the minimum capacity required for an automatic suggestion\n')
|
||||||
text += tr("Minimum capacity for /home partition: {}GiB\n").format(min_home_partition_size.format_size(Unit.GiB))
|
text += tr('Minimum capacity for /home partition: {}GiB\n').format(min_home_partition_size.format_size(Unit.GiB))
|
||||||
text += tr("Minimum capacity for Arch Linux partition: {}GiB").format(desired_root_partition_size.format_size(Unit.GiB))
|
text += tr('Minimum capacity for Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(Unit.GiB))
|
||||||
|
|
||||||
items = [MenuItem(tr("Continue"))]
|
items = [MenuItem(tr('Continue'))]
|
||||||
group = MenuItemGroup(items)
|
group = MenuItemGroup(items)
|
||||||
SelectMenu(group).run()
|
SelectMenu(group).run()
|
||||||
|
|
||||||
|
|
@ -480,11 +480,11 @@ def suggest_multi_disk_layout(
|
||||||
if filesystem_type == FilesystemType.Btrfs:
|
if filesystem_type == FilesystemType.Btrfs:
|
||||||
mount_options = select_mount_options()
|
mount_options = select_mount_options()
|
||||||
|
|
||||||
device_paths = ", ".join([str(d.device_info.path) for d in devices])
|
device_paths = ', '.join([str(d.device_info.path) for d in devices])
|
||||||
|
|
||||||
debug(f"Suggesting multi-disk-layout for devices: {device_paths}")
|
debug(f'Suggesting multi-disk-layout for devices: {device_paths}')
|
||||||
debug(f"/root: {root_device.device_info.path}")
|
debug(f'/root: {root_device.device_info.path}')
|
||||||
debug(f"/home: {home_device.device_info.path}")
|
debug(f'/home: {home_device.device_info.path}')
|
||||||
|
|
||||||
root_device_modification = DeviceModification(root_device, wipe=True)
|
root_device_modification = DeviceModification(root_device, wipe=True)
|
||||||
home_device_modification = DeviceModification(home_device, wipe=True)
|
home_device_modification = DeviceModification(home_device, wipe=True)
|
||||||
|
|
@ -512,7 +512,7 @@ def suggest_multi_disk_layout(
|
||||||
type=PartitionType.Primary,
|
type=PartitionType.Primary,
|
||||||
start=root_start,
|
start=root_start,
|
||||||
length=root_length,
|
length=root_length,
|
||||||
mountpoint=Path("/"),
|
mountpoint=Path('/'),
|
||||||
mount_options=mount_options,
|
mount_options=mount_options,
|
||||||
fs_type=filesystem_type,
|
fs_type=filesystem_type,
|
||||||
)
|
)
|
||||||
|
|
@ -534,7 +534,7 @@ def suggest_multi_disk_layout(
|
||||||
type=PartitionType.Primary,
|
type=PartitionType.Primary,
|
||||||
start=home_start,
|
start=home_start,
|
||||||
length=home_length,
|
length=home_length,
|
||||||
mountpoint=Path("/home"),
|
mountpoint=Path('/home'),
|
||||||
mount_options=mount_options,
|
mount_options=mount_options,
|
||||||
fs_type=filesystem_type,
|
fs_type=filesystem_type,
|
||||||
flags=flags,
|
flags=flags,
|
||||||
|
|
@ -547,10 +547,10 @@ def suggest_multi_disk_layout(
|
||||||
def suggest_lvm_layout(
|
def suggest_lvm_layout(
|
||||||
disk_config: DiskLayoutConfiguration,
|
disk_config: DiskLayoutConfiguration,
|
||||||
filesystem_type: FilesystemType | None = None,
|
filesystem_type: FilesystemType | None = None,
|
||||||
vg_grp_name: str = "ArchinstallVg",
|
vg_grp_name: str = 'ArchinstallVg',
|
||||||
) -> LvmConfiguration:
|
) -> LvmConfiguration:
|
||||||
if disk_config.config_type != DiskLayoutType.Default:
|
if disk_config.config_type != DiskLayoutType.Default:
|
||||||
raise ValueError("LVM suggested volumes are only available for default partitioning")
|
raise ValueError('LVM suggested volumes are only available for default partitioning')
|
||||||
|
|
||||||
using_subvolumes = False
|
using_subvolumes = False
|
||||||
btrfs_subvols = []
|
btrfs_subvols = []
|
||||||
|
|
@ -561,7 +561,7 @@ def suggest_lvm_layout(
|
||||||
filesystem_type = select_main_filesystem_format()
|
filesystem_type = select_main_filesystem_format()
|
||||||
|
|
||||||
if filesystem_type == FilesystemType.Btrfs:
|
if filesystem_type == FilesystemType.Btrfs:
|
||||||
prompt = tr("Would you like to use BTRFS subvolumes with a default structure?") + "\n"
|
prompt = tr('Would you like to use BTRFS subvolumes with a default structure?') + '\n'
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
group.set_focus_by_value(MenuItem.yes().value)
|
group.set_focus_by_value(MenuItem.yes().value)
|
||||||
|
|
||||||
|
|
@ -580,10 +580,10 @@ def suggest_lvm_layout(
|
||||||
|
|
||||||
if using_subvolumes:
|
if using_subvolumes:
|
||||||
btrfs_subvols = [
|
btrfs_subvols = [
|
||||||
SubvolumeModification(Path("@"), Path("/")),
|
SubvolumeModification(Path('@'), Path('/')),
|
||||||
SubvolumeModification(Path("@home"), Path("/home")),
|
SubvolumeModification(Path('@home'), Path('/home')),
|
||||||
SubvolumeModification(Path("@log"), Path("/var/log")),
|
SubvolumeModification(Path('@log'), Path('/var/log')),
|
||||||
SubvolumeModification(Path("@pkg"), Path("/var/cache/pacman/pkg")),
|
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
|
||||||
]
|
]
|
||||||
|
|
||||||
home_volume = False
|
home_volume = False
|
||||||
|
|
@ -599,7 +599,7 @@ def suggest_lvm_layout(
|
||||||
other_part.append(part)
|
other_part.append(part)
|
||||||
|
|
||||||
if not boot_part:
|
if not boot_part:
|
||||||
raise ValueError("Unable to find boot partition in partition modifications")
|
raise ValueError('Unable to find boot partition in partition modifications')
|
||||||
|
|
||||||
total_vol_available = sum(
|
total_vol_available = sum(
|
||||||
[p.length for p in other_part],
|
[p.length for p in other_part],
|
||||||
|
|
@ -612,10 +612,10 @@ def suggest_lvm_layout(
|
||||||
|
|
||||||
root_vol = LvmVolume(
|
root_vol = LvmVolume(
|
||||||
status=LvmVolumeStatus.Create,
|
status=LvmVolumeStatus.Create,
|
||||||
name="root",
|
name='root',
|
||||||
fs_type=filesystem_type,
|
fs_type=filesystem_type,
|
||||||
length=root_vol_size,
|
length=root_vol_size,
|
||||||
mountpoint=Path("/"),
|
mountpoint=Path('/'),
|
||||||
btrfs_subvols=btrfs_subvols,
|
btrfs_subvols=btrfs_subvols,
|
||||||
mount_options=mount_options,
|
mount_options=mount_options,
|
||||||
)
|
)
|
||||||
|
|
@ -625,10 +625,10 @@ def suggest_lvm_layout(
|
||||||
if home_volume:
|
if home_volume:
|
||||||
home_vol = LvmVolume(
|
home_vol = LvmVolume(
|
||||||
status=LvmVolumeStatus.Create,
|
status=LvmVolumeStatus.Create,
|
||||||
name="home",
|
name='home',
|
||||||
fs_type=filesystem_type,
|
fs_type=filesystem_type,
|
||||||
length=home_vol_size,
|
length=home_vol_size,
|
||||||
mountpoint=Path("/home"),
|
mountpoint=Path('/home'),
|
||||||
)
|
)
|
||||||
|
|
||||||
lvm_vol_group.volumes.append(home_vol)
|
lvm_vol_group.volumes.append(home_vol)
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,18 @@ from ..translationhandler import Language
|
||||||
|
|
||||||
|
|
||||||
class PostInstallationAction(Enum):
|
class PostInstallationAction(Enum):
|
||||||
EXIT = tr("Exit archinstall")
|
EXIT = tr('Exit archinstall')
|
||||||
REBOOT = tr("Reboot system")
|
REBOOT = tr('Reboot system')
|
||||||
CHROOT = tr("chroot into installation for post-installation configurations")
|
CHROOT = tr('chroot into installation for post-installation configurations')
|
||||||
|
|
||||||
|
|
||||||
def ask_ntp(preset: bool = True) -> bool:
|
def ask_ntp(preset: bool = True) -> bool:
|
||||||
header = tr("Would you like to use automatic time synchronization (NTP) with the default time servers?\n") + "\n"
|
header = tr('Would you like to use automatic time synchronization (NTP) with the default time servers?\n') + '\n'
|
||||||
header += (
|
header += (
|
||||||
tr(
|
tr(
|
||||||
"Hardware time and other post-configuration steps might be required in order for NTP to work.\nFor more information, please check the Arch wiki",
|
'Hardware time and other post-configuration steps might be required in order for NTP to work.\nFor more information, please check the Arch wiki',
|
||||||
)
|
)
|
||||||
+ "\n"
|
+ '\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
preset_val = MenuItem.yes() if preset else MenuItem.no()
|
preset_val = MenuItem.yes() if preset else MenuItem.no()
|
||||||
|
|
@ -53,12 +53,12 @@ def ask_ntp(preset: bool = True) -> bool:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.item() == MenuItem.yes()
|
return result.item() == MenuItem.yes()
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
|
|
||||||
def ask_hostname(preset: str | None = None) -> str | None:
|
def ask_hostname(preset: str | None = None) -> str | None:
|
||||||
result = EditMenu(
|
result = EditMenu(
|
||||||
tr("Hostname"),
|
tr('Hostname'),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
default_text=preset,
|
default_text=preset,
|
||||||
|
|
@ -73,11 +73,11 @@ def ask_hostname(preset: str | None = None) -> str | None:
|
||||||
return None
|
return None
|
||||||
return hostname
|
return hostname
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
|
|
||||||
def ask_for_a_timezone(preset: str | None = None) -> str | None:
|
def ask_for_a_timezone(preset: str | None = None) -> str | None:
|
||||||
default = "UTC"
|
default = 'UTC'
|
||||||
timezones = list_timezones()
|
timezones = list_timezones()
|
||||||
|
|
||||||
items = [MenuItem(tz, value=tz) for tz in timezones]
|
items = [MenuItem(tz, value=tz) for tz in timezones]
|
||||||
|
|
@ -89,7 +89,7 @@ def ask_for_a_timezone(preset: str | None = None) -> str | None:
|
||||||
group,
|
group,
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
frame=FrameProperties.min(tr("Timezone")),
|
frame=FrameProperties.min(tr('Timezone')),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -113,7 +113,7 @@ def ask_for_audio_selection(preset: AudioConfiguration | None = None) -> AudioCo
|
||||||
group,
|
group,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Audio")),
|
frame=FrameProperties.min(tr('Audio')),
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
match result.type_:
|
match result.type_:
|
||||||
|
|
@ -122,7 +122,7 @@ def ask_for_audio_selection(preset: AudioConfiguration | None = None) -> AudioCo
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return AudioConfiguration(audio=result.get_value())
|
return AudioConfiguration(audio=result.get_value())
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
|
|
||||||
def select_language(preset: str | None = None) -> str | None:
|
def select_language(preset: str | None = None) -> str | None:
|
||||||
|
|
@ -133,7 +133,7 @@ def select_language(preset: str | None = None) -> str | None:
|
||||||
# raise Deprecated("select_language() has been deprecated, use select_kb_layout() instead.")
|
# raise Deprecated("select_language() has been deprecated, use select_kb_layout() instead.")
|
||||||
|
|
||||||
# No need to translate this i feel, as it's a short lived message.
|
# No need to translate this i feel, as it's a short lived message.
|
||||||
warn("select_language() is deprecated, use select_kb_layout() instead. select_language() will be removed in a future version")
|
warn('select_language() is deprecated, use select_kb_layout() instead. select_language() will be removed in a future version')
|
||||||
|
|
||||||
return select_kb_layout(preset)
|
return select_kb_layout(preset)
|
||||||
|
|
||||||
|
|
@ -147,9 +147,9 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
|
||||||
group = MenuItemGroup(items, sort_items=True)
|
group = MenuItemGroup(items, sort_items=True)
|
||||||
group.set_focus_by_value(preset)
|
group.set_focus_by_value(preset)
|
||||||
|
|
||||||
title = "NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n"
|
title = 'NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n'
|
||||||
title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n'
|
title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n'
|
||||||
title += "e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n"
|
title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n'
|
||||||
|
|
||||||
result = SelectMenu[Language](
|
result = SelectMenu[Language](
|
||||||
group,
|
group,
|
||||||
|
|
@ -157,7 +157,7 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
allow_reset=False,
|
allow_reset=False,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(header=tr("Select language")),
|
frame=FrameProperties.min(header=tr('Select language')),
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
match result.type_:
|
match result.type_:
|
||||||
|
|
@ -166,7 +166,7 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.get_value()
|
return result.get_value()
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Language selection not handled")
|
raise ValueError('Language selection not handled')
|
||||||
|
|
||||||
|
|
||||||
def ask_additional_packages_to_install(
|
def ask_additional_packages_to_install(
|
||||||
|
|
@ -175,18 +175,18 @@ def ask_additional_packages_to_install(
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
repositories |= {Repository.Core, Repository.Extra}
|
repositories |= {Repository.Core, Repository.Extra}
|
||||||
|
|
||||||
respos_text = ", ".join([r.value for r in repositories])
|
respos_text = ', '.join([r.value for r in repositories])
|
||||||
output = tr("Repositories: {}").format(respos_text) + "\n"
|
output = tr('Repositories: {}').format(respos_text) + '\n'
|
||||||
|
|
||||||
output += tr("Loading packages...")
|
output += tr('Loading packages...')
|
||||||
Tui.print(output, clear_screen=True)
|
Tui.print(output, clear_screen=True)
|
||||||
|
|
||||||
packages = list_available_packages(tuple(repositories))
|
packages = list_available_packages(tuple(repositories))
|
||||||
package_groups = PackageGroup.from_available_packages(packages)
|
package_groups = PackageGroup.from_available_packages(packages)
|
||||||
|
|
||||||
# Additional packages (with some light weight error handling for invalid package names)
|
# Additional packages (with some light weight error handling for invalid package names)
|
||||||
header = tr("Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.") + "\n"
|
header = tr('Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.') + '\n'
|
||||||
header += tr("Select any packages from the below list that should be installed additionally") + "\n"
|
header += tr('Select any packages from the below list that should be installed additionally') + '\n'
|
||||||
|
|
||||||
# there are over 15k packages so this needs to be quick
|
# there are over 15k packages so this needs to be quick
|
||||||
preset_packages: list[AvailablePackage | PackageGroup] = []
|
preset_packages: list[AvailablePackage | PackageGroup] = []
|
||||||
|
|
@ -224,9 +224,9 @@ def ask_additional_packages_to_install(
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
multi=True,
|
multi=True,
|
||||||
preview_frame=FrameProperties.max("Package info"),
|
preview_frame=FrameProperties.max('Package info'),
|
||||||
preview_style=PreviewStyle.RIGHT,
|
preview_style=PreviewStyle.RIGHT,
|
||||||
preview_size="auto",
|
preview_size='auto',
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
match result.type_:
|
match result.type_:
|
||||||
|
|
@ -242,10 +242,10 @@ def ask_additional_packages_to_install(
|
||||||
def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
|
def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
|
||||||
max_recommended = 5
|
max_recommended = 5
|
||||||
|
|
||||||
header = tr("This option enables the number of parallel downloads that can occur during package downloads") + "\n"
|
header = tr('This option enables the number of parallel downloads that can occur during package downloads') + '\n'
|
||||||
header += tr("Enter the number of parallel downloads to be enabled.\n\nNote:\n")
|
header += tr('Enter the number of parallel downloads to be enabled.\n\nNote:\n')
|
||||||
header += tr(" - Maximum recommended value : {} ( Allows {} parallel downloads at a time )").format(max_recommended, max_recommended) + "\n"
|
header += tr(' - Maximum recommended value : {} ( Allows {} parallel downloads at a time )').format(max_recommended, max_recommended) + '\n'
|
||||||
header += tr(" - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n")
|
header += tr(' - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n')
|
||||||
|
|
||||||
def validator(s: str) -> str | None:
|
def validator(s: str) -> str | None:
|
||||||
try:
|
try:
|
||||||
|
|
@ -255,10 +255,10 @@ def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return tr("Invalid download number")
|
return tr('Invalid download number')
|
||||||
|
|
||||||
result = EditMenu(
|
result = EditMenu(
|
||||||
tr("Number downloads"),
|
tr('Number downloads'),
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
|
|
@ -276,23 +276,23 @@ def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
|
||||||
case _:
|
case _:
|
||||||
assert_never(result.type_)
|
assert_never(result.type_)
|
||||||
|
|
||||||
pacman_conf_path = Path("/etc/pacman.conf")
|
pacman_conf_path = Path('/etc/pacman.conf')
|
||||||
with pacman_conf_path.open() as f:
|
with pacman_conf_path.open() as f:
|
||||||
pacman_conf = f.read().split("\n")
|
pacman_conf = f.read().split('\n')
|
||||||
|
|
||||||
with pacman_conf_path.open("w") as fwrite:
|
with pacman_conf_path.open('w') as fwrite:
|
||||||
for line in pacman_conf:
|
for line in pacman_conf:
|
||||||
if "ParallelDownloads" in line:
|
if 'ParallelDownloads' in line:
|
||||||
fwrite.write(f"ParallelDownloads = {downloads}\n")
|
fwrite.write(f'ParallelDownloads = {downloads}\n')
|
||||||
else:
|
else:
|
||||||
fwrite.write(f"{line}\n")
|
fwrite.write(f'{line}\n')
|
||||||
|
|
||||||
return downloads
|
return downloads
|
||||||
|
|
||||||
|
|
||||||
def ask_post_installation() -> PostInstallationAction:
|
def ask_post_installation() -> PostInstallationAction:
|
||||||
header = tr("Installation completed") + "\n\n"
|
header = tr('Installation completed') + '\n\n'
|
||||||
header += tr("What would you like to do next?") + "\n"
|
header += tr('What would you like to do next?') + '\n'
|
||||||
|
|
||||||
items = [MenuItem(action.value, value=action) for action in PostInstallationAction]
|
items = [MenuItem(action.value, value=action) for action in PostInstallationAction]
|
||||||
group = MenuItemGroup(items)
|
group = MenuItemGroup(items)
|
||||||
|
|
@ -308,11 +308,11 @@ def ask_post_installation() -> PostInstallationAction:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.get_value()
|
return result.get_value()
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Post installation action not handled")
|
raise ValueError('Post installation action not handled')
|
||||||
|
|
||||||
|
|
||||||
def ask_abort() -> None:
|
def ask_abort() -> None:
|
||||||
prompt = tr("Do you really want to abort?") + "\n"
|
prompt = tr('Do you really want to abort?') + '\n'
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
|
|
||||||
result = SelectMenu[bool](
|
result = SelectMenu[bool](
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,10 @@ from ..utils.util import get_password
|
||||||
class UserList(ListManager[User]):
|
class UserList(ListManager[User]):
|
||||||
def __init__(self, prompt: str, lusers: list[User]):
|
def __init__(self, prompt: str, lusers: list[User]):
|
||||||
self._actions = [
|
self._actions = [
|
||||||
tr("Add a user"),
|
tr('Add a user'),
|
||||||
tr("Change password"),
|
tr('Change password'),
|
||||||
tr("Promote/Demote user"),
|
tr('Promote/Demote user'),
|
||||||
tr("Delete User"),
|
tr('Delete User'),
|
||||||
]
|
]
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|
@ -44,8 +44,8 @@ class UserList(ListManager[User]):
|
||||||
data = [d for d in data if d.username != new_user.username]
|
data = [d for d in data if d.username != new_user.username]
|
||||||
data += [new_user]
|
data += [new_user]
|
||||||
elif action == self._actions[1] and entry: # change password
|
elif action == self._actions[1] and entry: # change password
|
||||||
header = f"{tr('User')}: {entry.username}\n"
|
header = f'{tr("User")}: {entry.username}\n'
|
||||||
new_password = get_password(tr("Password"), header=header)
|
new_password = get_password(tr('Password'), header=header)
|
||||||
|
|
||||||
if new_password:
|
if new_password:
|
||||||
user = next(filter(lambda x: x == entry, data))
|
user = next(filter(lambda x: x == entry, data))
|
||||||
|
|
@ -59,13 +59,13 @@ class UserList(ListManager[User]):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def _check_for_correct_username(self, username: str) -> str | None:
|
def _check_for_correct_username(self, username: str) -> str | None:
|
||||||
if re.match(r"^[a-z_][a-z0-9_-]*\$?$", username) and len(username) <= 32:
|
if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32:
|
||||||
return None
|
return None
|
||||||
return tr("The username you entered is invalid")
|
return tr('The username you entered is invalid')
|
||||||
|
|
||||||
def _add_user(self) -> User | None:
|
def _add_user(self) -> User | None:
|
||||||
editResult = EditMenu(
|
editResult = EditMenu(
|
||||||
tr("Username"),
|
tr('Username'),
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
validator=self._check_for_correct_username,
|
validator=self._check_for_correct_username,
|
||||||
).input()
|
).input()
|
||||||
|
|
@ -76,16 +76,16 @@ class UserList(ListManager[User]):
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
username = editResult.text()
|
username = editResult.text()
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
header = f"{tr('Username')}: {username}\n"
|
header = f'{tr("Username")}: {username}\n'
|
||||||
|
|
||||||
password = get_password(tr("Password"), header=header, allow_skip=True)
|
password = get_password(tr('Password'), header=header, allow_skip=True)
|
||||||
|
|
||||||
if not password:
|
if not password:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
header += f"{tr('Password')}: {password.hidden()}\n\n"
|
header += f'{tr("Password")}: {password.hidden()}\n\n'
|
||||||
header += str(tr('Should "{}" be a superuser (sudo)?\n')).format(username)
|
header += str(tr('Should "{}" be a superuser (sudo)?\n')).format(username)
|
||||||
|
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
|
|
@ -105,11 +105,11 @@ class UserList(ListManager[User]):
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
sudo = result.item() == MenuItem.yes()
|
sudo = result.item() == MenuItem.yes()
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
return User(username, password, sudo)
|
return User(username, password, sudo)
|
||||||
|
|
||||||
|
|
||||||
def ask_for_additional_users(prompt: str = "", defined_users: list[User] = []) -> list[User]:
|
def ask_for_additional_users(prompt: str = '', defined_users: list[User] = []) -> list[User]:
|
||||||
users = UserList(prompt, defined_users).run()
|
users = UserList(prompt, defined_users).run()
|
||||||
return users
|
return users
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ from ..networking import list_interfaces
|
||||||
class ManualNetworkConfig(ListManager[Nic]):
|
class ManualNetworkConfig(ListManager[Nic]):
|
||||||
def __init__(self, prompt: str, preset: list[Nic]):
|
def __init__(self, prompt: str, preset: list[Nic]):
|
||||||
self._actions = [
|
self._actions = [
|
||||||
tr("Add interface"),
|
tr('Add interface'),
|
||||||
tr("Edit interface"),
|
tr('Edit interface'),
|
||||||
tr("Delete interface"),
|
tr('Delete interface'),
|
||||||
]
|
]
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|
@ -31,7 +31,7 @@ class ManualNetworkConfig(ListManager[Nic]):
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def selected_action_display(self, selection: Nic) -> str:
|
def selected_action_display(self, selection: Nic) -> str:
|
||||||
return selection.iface if selection.iface else ""
|
return selection.iface if selection.iface else ''
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def handle_action(self, action: str, entry: Nic | None, data: list[Nic]) -> list[Nic]:
|
def handle_action(self, action: str, entry: Nic | None, data: list[Nic]) -> list[Nic]:
|
||||||
|
|
@ -67,7 +67,7 @@ class ManualNetworkConfig(ListManager[Nic]):
|
||||||
result = SelectMenu[str](
|
result = SelectMenu[str](
|
||||||
group,
|
group,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Interfaces")),
|
frame=FrameProperties.min(tr('Interfaces')),
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ class ManualNetworkConfig(ListManager[Nic]):
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.get_value()
|
return result.get_value()
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
def _get_ip_address(
|
def _get_ip_address(
|
||||||
self,
|
self,
|
||||||
|
|
@ -89,7 +89,7 @@ class ManualNetworkConfig(ListManager[Nic]):
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
def validator(ip: str) -> str | None:
|
def validator(ip: str) -> str | None:
|
||||||
if multi:
|
if multi:
|
||||||
ips = ip.split(" ")
|
ips = ip.split(' ')
|
||||||
else:
|
else:
|
||||||
ips = [ip]
|
ips = [ip]
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ class ManualNetworkConfig(ListManager[Nic]):
|
||||||
ipaddress.ip_interface(ip)
|
ipaddress.ip_interface(ip)
|
||||||
return None
|
return None
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return tr("You need to enter a valid IP in IP-config mode")
|
return tr('You need to enter a valid IP in IP-config mode')
|
||||||
|
|
||||||
result = EditMenu(
|
result = EditMenu(
|
||||||
title,
|
title,
|
||||||
|
|
@ -114,14 +114,14 @@ class ManualNetworkConfig(ListManager[Nic]):
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.text()
|
return result.text()
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
def _edit_iface(self, edit_nic: Nic) -> Nic:
|
def _edit_iface(self, edit_nic: Nic) -> Nic:
|
||||||
iface_name = edit_nic.iface
|
iface_name = edit_nic.iface
|
||||||
modes = ["DHCP (auto detect)", "IP (static)"]
|
modes = ['DHCP (auto detect)', 'IP (static)']
|
||||||
default_mode = "DHCP (auto detect)"
|
default_mode = 'DHCP (auto detect)'
|
||||||
|
|
||||||
header = tr('Select which mode to configure for "{}"').format(iface_name) + "\n"
|
header = tr('Select which mode to configure for "{}"').format(iface_name) + '\n'
|
||||||
items = [MenuItem(m, value=m) for m in modes]
|
items = [MenuItem(m, value=m) for m in modes]
|
||||||
group = MenuItemGroup(items, sort_items=True)
|
group = MenuItemGroup(items, sort_items=True)
|
||||||
group.set_default_by_value(default_mode)
|
group.set_default_by_value(default_mode)
|
||||||
|
|
@ -131,34 +131,34 @@ class ManualNetworkConfig(ListManager[Nic]):
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=False,
|
allow_skip=False,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Modes")),
|
frame=FrameProperties.min(tr('Modes')),
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
match result.type_:
|
match result.type_:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
mode = result.get_value()
|
mode = result.get_value()
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
case ResultType.Skip:
|
case ResultType.Skip:
|
||||||
raise ValueError("The mode menu should not be skippable")
|
raise ValueError('The mode menu should not be skippable')
|
||||||
case _:
|
case _:
|
||||||
assert_never(result.type_)
|
assert_never(result.type_)
|
||||||
|
|
||||||
if mode == "IP (static)":
|
if mode == 'IP (static)':
|
||||||
header = tr("Enter the IP and subnet for {} (example: 192.168.0.5/24): ").format(iface_name) + "\n"
|
header = tr('Enter the IP and subnet for {} (example: 192.168.0.5/24): ').format(iface_name) + '\n'
|
||||||
ip = self._get_ip_address(tr("IP address"), header, False, False)
|
ip = self._get_ip_address(tr('IP address'), header, False, False)
|
||||||
|
|
||||||
header = tr("Enter your gateway (router) IP address (leave blank for none)") + "\n"
|
header = tr('Enter your gateway (router) IP address (leave blank for none)') + '\n'
|
||||||
gateway = self._get_ip_address(tr("Gateway address"), header, True, False)
|
gateway = self._get_ip_address(tr('Gateway address'), header, True, False)
|
||||||
|
|
||||||
if edit_nic.dns:
|
if edit_nic.dns:
|
||||||
display_dns = " ".join(edit_nic.dns)
|
display_dns = ' '.join(edit_nic.dns)
|
||||||
else:
|
else:
|
||||||
display_dns = None
|
display_dns = None
|
||||||
|
|
||||||
header = tr("Enter your DNS servers with space separated (leave blank for none)") + "\n"
|
header = tr('Enter your DNS servers with space separated (leave blank for none)') + '\n'
|
||||||
dns_servers = self._get_ip_address(
|
dns_servers = self._get_ip_address(
|
||||||
tr("DNS servers"),
|
tr('DNS servers'),
|
||||||
header,
|
header,
|
||||||
True,
|
True,
|
||||||
True,
|
True,
|
||||||
|
|
@ -167,7 +167,7 @@ class ManualNetworkConfig(ListManager[Nic]):
|
||||||
|
|
||||||
dns = []
|
dns = []
|
||||||
if dns_servers is not None:
|
if dns_servers is not None:
|
||||||
dns = dns_servers.split(" ")
|
dns = dns_servers.split(' ')
|
||||||
|
|
||||||
return Nic(iface=iface_name, ip=ip, gateway=gateway, dns=dns, dhcp=False)
|
return Nic(iface=iface_name, ip=ip, gateway=gateway, dns=dns, dhcp=False)
|
||||||
else:
|
else:
|
||||||
|
|
@ -189,7 +189,7 @@ def ask_to_configure_network(preset: NetworkConfiguration | None) -> NetworkConf
|
||||||
result = SelectMenu[NetworkConfiguration](
|
result = SelectMenu[NetworkConfiguration](
|
||||||
group,
|
group,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Network configuration")),
|
frame=FrameProperties.min(tr('Network configuration')),
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
).run()
|
).run()
|
||||||
|
|
@ -209,7 +209,7 @@ def ask_to_configure_network(preset: NetworkConfiguration | None) -> NetworkConf
|
||||||
return NetworkConfiguration(NicType.NM)
|
return NetworkConfiguration(NicType.NM)
|
||||||
case NicType.MANUAL:
|
case NicType.MANUAL:
|
||||||
preset_nics = preset.nics if preset else []
|
preset_nics = preset.nics if preset else []
|
||||||
nics = ManualNetworkConfig(tr("Configure interfaces"), preset_nics).run()
|
nics = ManualNetworkConfig(tr('Configure interfaces'), preset_nics).run()
|
||||||
|
|
||||||
if nics:
|
if nics:
|
||||||
return NetworkConfiguration(NicType.MANUAL, nics)
|
return NetworkConfiguration(NicType.MANUAL, nics)
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ def select_kernel(preset: list[str] = []) -> list[str]:
|
||||||
:return: The string as a selected kernel
|
:return: The string as a selected kernel
|
||||||
:rtype: string
|
:rtype: string
|
||||||
"""
|
"""
|
||||||
kernels = ["linux", "linux-lts", "linux-zen", "linux-hardened"]
|
kernels = ['linux', 'linux-lts', 'linux-zen', 'linux-hardened']
|
||||||
default_kernel = "linux"
|
default_kernel = 'linux'
|
||||||
|
|
||||||
items = [MenuItem(k, value=k) for k in kernels]
|
items = [MenuItem(k, value=k) for k in kernels]
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ def select_kernel(preset: list[str] = []) -> list[str]:
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Kernel")),
|
frame=FrameProperties.min(tr('Kernel')),
|
||||||
multi=True,
|
multi=True,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
|
||||||
if not SysInfo.has_uefi():
|
if not SysInfo.has_uefi():
|
||||||
options = [Bootloader.Grub, Bootloader.Limine]
|
options = [Bootloader.Grub, Bootloader.Limine]
|
||||||
default = Bootloader.Grub
|
default = Bootloader.Grub
|
||||||
header = tr("UEFI is not detected and some options are disabled")
|
header = tr('UEFI is not detected and some options are disabled')
|
||||||
else:
|
else:
|
||||||
options = [b for b in Bootloader]
|
options = [b for b in Bootloader]
|
||||||
default = Bootloader.Systemd
|
default = Bootloader.Systemd
|
||||||
|
|
@ -65,7 +65,7 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
|
||||||
group,
|
group,
|
||||||
header=header,
|
header=header,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Bootloader")),
|
frame=FrameProperties.min(tr('Bootloader')),
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -75,11 +75,11 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.get_value()
|
return result.get_value()
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
|
|
||||||
def ask_for_uki(preset: bool = True) -> bool:
|
def ask_for_uki(preset: bool = True) -> bool:
|
||||||
prompt = tr("Would you like to use unified kernel images?") + "\n"
|
prompt = tr('Would you like to use unified kernel images?') + '\n'
|
||||||
|
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
group.set_focus_by_value(preset)
|
group.set_focus_by_value(preset)
|
||||||
|
|
@ -99,7 +99,7 @@ def ask_for_uki(preset: bool = True) -> bool:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.item() == MenuItem.yes()
|
return result.item() == MenuItem.yes()
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
|
|
||||||
def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None) -> GfxDriver | None:
|
def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None) -> GfxDriver | None:
|
||||||
|
|
@ -120,22 +120,22 @@ def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None
|
||||||
if preset is not None:
|
if preset is not None:
|
||||||
group.set_focus_by_value(preset)
|
group.set_focus_by_value(preset)
|
||||||
|
|
||||||
header = ""
|
header = ''
|
||||||
if SysInfo.has_amd_graphics():
|
if SysInfo.has_amd_graphics():
|
||||||
header += tr("For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options.") + "\n"
|
header += tr('For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options.') + '\n'
|
||||||
if SysInfo.has_intel_graphics():
|
if SysInfo.has_intel_graphics():
|
||||||
header += tr("For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n")
|
header += tr('For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n')
|
||||||
if SysInfo.has_nvidia_graphics():
|
if SysInfo.has_nvidia_graphics():
|
||||||
header += tr("For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n")
|
header += tr('For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n')
|
||||||
|
|
||||||
result = SelectMenu[GfxDriver](
|
result = SelectMenu[GfxDriver](
|
||||||
group,
|
group,
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
preview_size="auto",
|
preview_size='auto',
|
||||||
preview_style=PreviewStyle.BOTTOM,
|
preview_style=PreviewStyle.BOTTOM,
|
||||||
preview_frame=FrameProperties(tr("Info"), h_frame_style=FrameStyle.MIN),
|
preview_frame=FrameProperties(tr('Info'), h_frame_style=FrameStyle.MIN),
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
match result.type_:
|
match result.type_:
|
||||||
|
|
@ -153,7 +153,7 @@ def ask_for_swap(preset: bool = True) -> bool:
|
||||||
else:
|
else:
|
||||||
default_item = MenuItem.no()
|
default_item = MenuItem.no()
|
||||||
|
|
||||||
prompt = tr("Would you like to use swap on zram?") + "\n"
|
prompt = tr('Would you like to use swap on zram?') + '\n'
|
||||||
|
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
group.set_focus_by_value(default_item)
|
group.set_focus_by_value(default_item)
|
||||||
|
|
@ -173,6 +173,6 @@ def ask_for_swap(preset: bool = True) -> bool:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.item() == MenuItem.yes()
|
return result.item() == MenuItem.yes()
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
return preset
|
return preset
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ from .utils import (
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"list_keyboard_languages",
|
'list_keyboard_languages',
|
||||||
"list_locales",
|
'list_locales',
|
||||||
"list_timezones",
|
'list_timezones',
|
||||||
"list_x11_keyboard_languages",
|
'list_x11_keyboard_languages',
|
||||||
"set_kb_layout",
|
'set_kb_layout',
|
||||||
"verify_keyboard_layout",
|
'verify_keyboard_layout',
|
||||||
"verify_x11_keyboard_layout",
|
'verify_x11_keyboard_layout',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -29,33 +29,33 @@ class LocaleMenu(AbstractSubMenu[LocaleConfiguration]):
|
||||||
def _define_menu_options(self) -> list[MenuItem]:
|
def _define_menu_options(self) -> list[MenuItem]:
|
||||||
return [
|
return [
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Keyboard layout"),
|
text=tr('Keyboard layout'),
|
||||||
action=self._select_kb_layout,
|
action=self._select_kb_layout,
|
||||||
value=self._locale_conf.kb_layout,
|
value=self._locale_conf.kb_layout,
|
||||||
preview_action=self._prev_locale,
|
preview_action=self._prev_locale,
|
||||||
key="kb_layout",
|
key='kb_layout',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Locale language"),
|
text=tr('Locale language'),
|
||||||
action=select_locale_lang,
|
action=select_locale_lang,
|
||||||
value=self._locale_conf.sys_lang,
|
value=self._locale_conf.sys_lang,
|
||||||
preview_action=self._prev_locale,
|
preview_action=self._prev_locale,
|
||||||
key="sys_lang",
|
key='sys_lang',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Locale encoding"),
|
text=tr('Locale encoding'),
|
||||||
action=select_locale_enc,
|
action=select_locale_enc,
|
||||||
value=self._locale_conf.sys_enc,
|
value=self._locale_conf.sys_enc,
|
||||||
preview_action=self._prev_locale,
|
preview_action=self._prev_locale,
|
||||||
key="sys_enc",
|
key='sys_enc',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def _prev_locale(self, item: MenuItem) -> str | None:
|
def _prev_locale(self, item: MenuItem) -> str | None:
|
||||||
temp_locale = LocaleConfiguration(
|
temp_locale = LocaleConfiguration(
|
||||||
self._menu_item_group.find_by_key("kb_layout").get_value(),
|
self._menu_item_group.find_by_key('kb_layout').get_value(),
|
||||||
self._menu_item_group.find_by_key("sys_lang").get_value(),
|
self._menu_item_group.find_by_key('sys_lang').get_value(),
|
||||||
self._menu_item_group.find_by_key("sys_enc").get_value(),
|
self._menu_item_group.find_by_key('sys_enc').get_value(),
|
||||||
)
|
)
|
||||||
return temp_locale.preview()
|
return temp_locale.preview()
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ def select_locale_lang(preset: str | None = None) -> str | None:
|
||||||
result = SelectMenu[str](
|
result = SelectMenu[str](
|
||||||
group,
|
group,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Locale language")),
|
frame=FrameProperties.min(tr('Locale language')),
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -92,7 +92,7 @@ def select_locale_lang(preset: str | None = None) -> str | None:
|
||||||
case ResultType.Skip:
|
case ResultType.Skip:
|
||||||
return preset
|
return preset
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
|
|
||||||
def select_locale_enc(preset: str | None = None) -> str | None:
|
def select_locale_enc(preset: str | None = None) -> str | None:
|
||||||
|
|
@ -106,7 +106,7 @@ def select_locale_enc(preset: str | None = None) -> str | None:
|
||||||
result = SelectMenu[str](
|
result = SelectMenu[str](
|
||||||
group,
|
group,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Locale encoding")),
|
frame=FrameProperties.min(tr('Locale encoding')),
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -116,7 +116,7 @@ def select_locale_enc(preset: str | None = None) -> str | None:
|
||||||
case ResultType.Skip:
|
case ResultType.Skip:
|
||||||
return preset
|
return preset
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
|
|
||||||
def select_kb_layout(preset: str | None = None) -> str | None:
|
def select_kb_layout(preset: str | None = None) -> str | None:
|
||||||
|
|
@ -138,7 +138,7 @@ def select_kb_layout(preset: str | None = None) -> str | None:
|
||||||
result = SelectMenu[str](
|
result = SelectMenu[str](
|
||||||
group,
|
group,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Keyboard layout")),
|
frame=FrameProperties.min(tr('Keyboard layout')),
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -148,6 +148,6 @@ def select_kb_layout(preset: str | None = None) -> str | None:
|
||||||
case ResultType.Skip:
|
case ResultType.Skip:
|
||||||
return preset
|
return preset
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ from ..output import error
|
||||||
def list_keyboard_languages() -> list[str]:
|
def list_keyboard_languages() -> list[str]:
|
||||||
return (
|
return (
|
||||||
SysCommand(
|
SysCommand(
|
||||||
"localectl --no-pager list-keymaps",
|
'localectl --no-pager list-keymaps',
|
||||||
environment_vars={"SYSTEMD_COLORS": "0"},
|
environment_vars={'SYSTEMD_COLORS': '0'},
|
||||||
)
|
)
|
||||||
.decode()
|
.decode()
|
||||||
.splitlines()
|
.splitlines()
|
||||||
|
|
@ -17,9 +17,9 @@ def list_keyboard_languages() -> list[str]:
|
||||||
def list_locales() -> list[str]:
|
def list_locales() -> list[str]:
|
||||||
locales = []
|
locales = []
|
||||||
|
|
||||||
with open("/usr/share/i18n/SUPPORTED") as file:
|
with open('/usr/share/i18n/SUPPORTED') as file:
|
||||||
for line in file:
|
for line in file:
|
||||||
if line != "C.UTF-8 UTF-8\n":
|
if line != 'C.UTF-8 UTF-8\n':
|
||||||
locales.append(line.rstrip())
|
locales.append(line.rstrip())
|
||||||
|
|
||||||
return locales
|
return locales
|
||||||
|
|
@ -28,8 +28,8 @@ def list_locales() -> list[str]:
|
||||||
def list_x11_keyboard_languages() -> list[str]:
|
def list_x11_keyboard_languages() -> list[str]:
|
||||||
return (
|
return (
|
||||||
SysCommand(
|
SysCommand(
|
||||||
"localectl --no-pager list-x11-keymap-layouts",
|
'localectl --no-pager list-x11-keymap-layouts',
|
||||||
environment_vars={"SYSTEMD_COLORS": "0"},
|
environment_vars={'SYSTEMD_COLORS': '0'},
|
||||||
)
|
)
|
||||||
.decode()
|
.decode()
|
||||||
.splitlines()
|
.splitlines()
|
||||||
|
|
@ -54,26 +54,26 @@ def get_kb_layout() -> str:
|
||||||
try:
|
try:
|
||||||
lines = (
|
lines = (
|
||||||
SysCommand(
|
SysCommand(
|
||||||
"localectl --no-pager status",
|
'localectl --no-pager status',
|
||||||
environment_vars={"SYSTEMD_COLORS": "0"},
|
environment_vars={'SYSTEMD_COLORS': '0'},
|
||||||
)
|
)
|
||||||
.decode()
|
.decode()
|
||||||
.splitlines()
|
.splitlines()
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
vcline = ""
|
vcline = ''
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if "VC Keymap: " in line:
|
if 'VC Keymap: ' in line:
|
||||||
vcline = line
|
vcline = line
|
||||||
|
|
||||||
if vcline == "":
|
if vcline == '':
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
layout = vcline.split(": ")[1]
|
layout = vcline.split(': ')[1]
|
||||||
if not verify_keyboard_layout(layout):
|
if not verify_keyboard_layout(layout):
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
return layout
|
return layout
|
||||||
|
|
||||||
|
|
@ -81,11 +81,11 @@ def get_kb_layout() -> str:
|
||||||
def set_kb_layout(locale: str) -> bool:
|
def set_kb_layout(locale: str) -> bool:
|
||||||
if len(locale.strip()):
|
if len(locale.strip()):
|
||||||
if not verify_keyboard_layout(locale):
|
if not verify_keyboard_layout(locale):
|
||||||
error(f"Invalid keyboard locale specified: {locale}")
|
error(f'Invalid keyboard locale specified: {locale}')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
SysCommand(f"localectl set-keymap {locale}")
|
SysCommand(f'localectl set-keymap {locale}')
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
raise ServiceException(f"Unable to set locale '{locale}' for console: {err}")
|
raise ServiceException(f"Unable to set locale '{locale}' for console: {err}")
|
||||||
|
|
||||||
|
|
@ -97,8 +97,8 @@ def set_kb_layout(locale: str) -> bool:
|
||||||
def list_timezones() -> list[str]:
|
def list_timezones() -> list[str]:
|
||||||
return (
|
return (
|
||||||
SysCommand(
|
SysCommand(
|
||||||
"timedatectl --no-pager list-timezones",
|
'timedatectl --no-pager list-timezones',
|
||||||
environment_vars={"SYSTEMD_COLORS": "0"},
|
environment_vars={'SYSTEMD_COLORS': '0'},
|
||||||
)
|
)
|
||||||
.decode()
|
.decode()
|
||||||
.splitlines()
|
.splitlines()
|
||||||
|
|
|
||||||
|
|
@ -24,25 +24,25 @@ class Luks2:
|
||||||
@property
|
@property
|
||||||
def mapper_dev(self) -> Path | None:
|
def mapper_dev(self) -> Path | None:
|
||||||
if self.mapper_name:
|
if self.mapper_name:
|
||||||
return Path(f"/dev/mapper/{self.mapper_name}")
|
return Path(f'/dev/mapper/{self.mapper_name}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def isLuks(self) -> bool:
|
def isLuks(self) -> bool:
|
||||||
try:
|
try:
|
||||||
SysCommand(f"cryptsetup isLuks {self.luks_dev_path}")
|
SysCommand(f'cryptsetup isLuks {self.luks_dev_path}')
|
||||||
return True
|
return True
|
||||||
except SysCallError:
|
except SysCallError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def erase(self) -> None:
|
def erase(self) -> None:
|
||||||
debug(f"Erasing luks partition: {self.luks_dev_path}")
|
debug(f'Erasing luks partition: {self.luks_dev_path}')
|
||||||
worker = SysCommandWorker(f"cryptsetup erase {self.luks_dev_path}")
|
worker = SysCommandWorker(f'cryptsetup erase {self.luks_dev_path}')
|
||||||
worker.poll()
|
worker.poll()
|
||||||
worker.write(b"YES\n", line_ending=False)
|
worker.write(b'YES\n', line_ending=False)
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if self.luks_dev_path is None:
|
if self.luks_dev_path is None:
|
||||||
raise ValueError("Partition must have a path set")
|
raise ValueError('Partition must have a path set')
|
||||||
|
|
||||||
def __enter__(self) -> None:
|
def __enter__(self) -> None:
|
||||||
self.unlock(self.key_file)
|
self.unlock(self.key_file)
|
||||||
|
|
@ -53,12 +53,12 @@ class Luks2:
|
||||||
|
|
||||||
def _password_bytes(self) -> bytes:
|
def _password_bytes(self) -> bytes:
|
||||||
if not self.password:
|
if not self.password:
|
||||||
raise ValueError("Password for luks2 device was not specified")
|
raise ValueError('Password for luks2 device was not specified')
|
||||||
|
|
||||||
if isinstance(self.password, bytes):
|
if isinstance(self.password, bytes):
|
||||||
return self.password
|
return self.password
|
||||||
else:
|
else:
|
||||||
return bytes(self.password.plaintext, "UTF-8")
|
return bytes(self.password.plaintext, 'UTF-8')
|
||||||
|
|
||||||
def _get_passphrase_args(
|
def _get_passphrase_args(
|
||||||
self,
|
self,
|
||||||
|
|
@ -67,42 +67,42 @@ class Luks2:
|
||||||
key_file = key_file or self.key_file
|
key_file = key_file or self.key_file
|
||||||
|
|
||||||
if key_file:
|
if key_file:
|
||||||
return ["--key-file", str(key_file)], None
|
return ['--key-file', str(key_file)], None
|
||||||
|
|
||||||
return [], self._password_bytes()
|
return [], self._password_bytes()
|
||||||
|
|
||||||
def encrypt(
|
def encrypt(
|
||||||
self,
|
self,
|
||||||
key_size: int = 512,
|
key_size: int = 512,
|
||||||
hash_type: str = "sha512",
|
hash_type: str = 'sha512',
|
||||||
iter_time: int = 10000,
|
iter_time: int = 10000,
|
||||||
key_file: Path | None = None,
|
key_file: Path | None = None,
|
||||||
) -> Path | None:
|
) -> Path | None:
|
||||||
debug(f"Luks2 encrypting: {self.luks_dev_path}")
|
debug(f'Luks2 encrypting: {self.luks_dev_path}')
|
||||||
|
|
||||||
key_file_arg, passphrase = self._get_passphrase_args(key_file)
|
key_file_arg, passphrase = self._get_passphrase_args(key_file)
|
||||||
|
|
||||||
cmd = [
|
cmd = [
|
||||||
"cryptsetup",
|
'cryptsetup',
|
||||||
"--batch-mode",
|
'--batch-mode',
|
||||||
"--verbose",
|
'--verbose',
|
||||||
"--type",
|
'--type',
|
||||||
"luks2",
|
'luks2',
|
||||||
"--pbkdf",
|
'--pbkdf',
|
||||||
"argon2id",
|
'argon2id',
|
||||||
"--hash",
|
'--hash',
|
||||||
hash_type,
|
hash_type,
|
||||||
"--key-size",
|
'--key-size',
|
||||||
str(key_size),
|
str(key_size),
|
||||||
"--iter-time",
|
'--iter-time',
|
||||||
str(iter_time),
|
str(iter_time),
|
||||||
*key_file_arg,
|
*key_file_arg,
|
||||||
"--use-urandom",
|
'--use-urandom',
|
||||||
"luksFormat",
|
'luksFormat',
|
||||||
str(self.luks_dev_path),
|
str(self.luks_dev_path),
|
||||||
]
|
]
|
||||||
|
|
||||||
debug(f"cryptsetup format: {shlex.join(cmd)}")
|
debug(f'cryptsetup format: {shlex.join(cmd)}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = run(cmd, input_data=passphrase)
|
result = run(cmd, input_data=passphrase)
|
||||||
|
|
@ -110,23 +110,23 @@ class Luks2:
|
||||||
output = err.stdout.decode().rstrip()
|
output = err.stdout.decode().rstrip()
|
||||||
raise DiskError(f'Could not encrypt volume "{self.luks_dev_path}": {output}')
|
raise DiskError(f'Could not encrypt volume "{self.luks_dev_path}": {output}')
|
||||||
|
|
||||||
debug(f"cryptsetup luksFormat output: {result.stdout.decode().rstrip()}")
|
debug(f'cryptsetup luksFormat output: {result.stdout.decode().rstrip()}')
|
||||||
|
|
||||||
self.key_file = key_file
|
self.key_file = key_file
|
||||||
|
|
||||||
return key_file
|
return key_file
|
||||||
|
|
||||||
def _get_luks_uuid(self) -> str:
|
def _get_luks_uuid(self) -> str:
|
||||||
command = f"cryptsetup luksUUID {self.luks_dev_path}"
|
command = f'cryptsetup luksUUID {self.luks_dev_path}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return SysCommand(command).decode()
|
return SysCommand(command).decode()
|
||||||
except SysCallError as err:
|
except SysCallError as err:
|
||||||
info(f"Unable to get UUID for Luks device: {self.luks_dev_path}")
|
info(f'Unable to get UUID for Luks device: {self.luks_dev_path}')
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
def is_unlocked(self) -> bool:
|
def is_unlocked(self) -> bool:
|
||||||
return self.mapper_name is not None and Path(f"/dev/mapper/{self.mapper_name}").exists()
|
return self.mapper_name is not None and Path(f'/dev/mapper/{self.mapper_name}').exists()
|
||||||
|
|
||||||
def unlock(self, key_file: Path | None = None) -> None:
|
def unlock(self, key_file: Path | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -136,29 +136,29 @@ class Luks2:
|
||||||
:param key_file: An alternative key file
|
:param key_file: An alternative key file
|
||||||
:type key_file: Path
|
:type key_file: Path
|
||||||
"""
|
"""
|
||||||
debug(f"Unlocking luks2 device: {self.luks_dev_path}")
|
debug(f'Unlocking luks2 device: {self.luks_dev_path}')
|
||||||
|
|
||||||
if not self.mapper_name:
|
if not self.mapper_name:
|
||||||
raise ValueError("mapper name missing")
|
raise ValueError('mapper name missing')
|
||||||
|
|
||||||
key_file_arg, passphrase = self._get_passphrase_args(key_file)
|
key_file_arg, passphrase = self._get_passphrase_args(key_file)
|
||||||
|
|
||||||
cmd = [
|
cmd = [
|
||||||
"cryptsetup",
|
'cryptsetup',
|
||||||
"open",
|
'open',
|
||||||
str(self.luks_dev_path),
|
str(self.luks_dev_path),
|
||||||
str(self.mapper_name),
|
str(self.mapper_name),
|
||||||
*key_file_arg,
|
*key_file_arg,
|
||||||
"--type",
|
'--type',
|
||||||
"luks2",
|
'luks2',
|
||||||
]
|
]
|
||||||
|
|
||||||
result = run(cmd, input_data=passphrase)
|
result = run(cmd, input_data=passphrase)
|
||||||
|
|
||||||
debug(f"cryptsetup open output: {result.stdout.decode().rstrip()}")
|
debug(f'cryptsetup open output: {result.stdout.decode().rstrip()}')
|
||||||
|
|
||||||
if not self.mapper_dev or not self.mapper_dev.is_symlink():
|
if not self.mapper_dev or not self.mapper_dev.is_symlink():
|
||||||
raise DiskError(f"Failed to open luks2 device: {self.luks_dev_path}")
|
raise DiskError(f'Failed to open luks2 device: {self.luks_dev_path}')
|
||||||
|
|
||||||
def lock(self) -> None:
|
def lock(self) -> None:
|
||||||
umount(self.luks_dev_path)
|
umount(self.luks_dev_path)
|
||||||
|
|
@ -171,32 +171,32 @@ class Luks2:
|
||||||
for child in lsblk_info.children:
|
for child in lsblk_info.children:
|
||||||
# Unmount the child location
|
# Unmount the child location
|
||||||
for mountpoint in child.mountpoints:
|
for mountpoint in child.mountpoints:
|
||||||
debug(f"Unmounting {mountpoint}")
|
debug(f'Unmounting {mountpoint}')
|
||||||
umount(mountpoint, recursive=True)
|
umount(mountpoint, recursive=True)
|
||||||
|
|
||||||
# And close it if possible.
|
# And close it if possible.
|
||||||
debug(f"Closing crypt device {child.name}")
|
debug(f'Closing crypt device {child.name}')
|
||||||
SysCommand(f"cryptsetup close {child.name}")
|
SysCommand(f'cryptsetup close {child.name}')
|
||||||
|
|
||||||
def create_keyfile(self, target_path: Path, override: bool = False) -> None:
|
def create_keyfile(self, target_path: Path, override: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Routine to create keyfiles, so it can be moved elsewhere
|
Routine to create keyfiles, so it can be moved elsewhere
|
||||||
"""
|
"""
|
||||||
if self.mapper_name is None:
|
if self.mapper_name is None:
|
||||||
raise ValueError("Mapper name must be provided")
|
raise ValueError('Mapper name must be provided')
|
||||||
|
|
||||||
# Once we store the key as ../xyzloop.key systemd-cryptsetup can
|
# Once we store the key as ../xyzloop.key systemd-cryptsetup can
|
||||||
# automatically load this key if we name the device to "xyzloop"
|
# automatically load this key if we name the device to "xyzloop"
|
||||||
kf_path = Path(f"/etc/cryptsetup-keys.d/{self.mapper_name}.key")
|
kf_path = Path(f'/etc/cryptsetup-keys.d/{self.mapper_name}.key')
|
||||||
key_file = target_path / kf_path.relative_to(kf_path.root)
|
key_file = target_path / kf_path.relative_to(kf_path.root)
|
||||||
crypttab_path = target_path / "etc/crypttab"
|
crypttab_path = target_path / 'etc/crypttab'
|
||||||
|
|
||||||
if key_file.exists():
|
if key_file.exists():
|
||||||
if not override:
|
if not override:
|
||||||
info(f"Key file {key_file} already exists, keeping existing")
|
info(f'Key file {key_file} already exists, keeping existing')
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
info(f"Key file {key_file} already exists, overriding")
|
info(f'Key file {key_file} already exists, overriding')
|
||||||
|
|
||||||
key_file.parent.mkdir(parents=True, exist_ok=True)
|
key_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
@ -206,22 +206,22 @@ class Luks2:
|
||||||
key_file.chmod(0o400)
|
key_file.chmod(0o400)
|
||||||
|
|
||||||
self._add_key(key_file)
|
self._add_key(key_file)
|
||||||
self._crypttab(crypttab_path, kf_path, options=["luks", "key-slot=1"])
|
self._crypttab(crypttab_path, kf_path, options=['luks', 'key-slot=1'])
|
||||||
|
|
||||||
def _add_key(self, key_file: Path) -> None:
|
def _add_key(self, key_file: Path) -> None:
|
||||||
debug(f"Adding additional key-file {key_file}")
|
debug(f'Adding additional key-file {key_file}')
|
||||||
|
|
||||||
command = f"cryptsetup -q -v luksAddKey {self.luks_dev_path} {key_file}"
|
command = f'cryptsetup -q -v luksAddKey {self.luks_dev_path} {key_file}'
|
||||||
worker = SysCommandWorker(command)
|
worker = SysCommandWorker(command)
|
||||||
pw_injected = False
|
pw_injected = False
|
||||||
|
|
||||||
while worker.is_alive():
|
while worker.is_alive():
|
||||||
if b"Enter any existing passphrase" in worker and pw_injected is False:
|
if b'Enter any existing passphrase' in worker and pw_injected is False:
|
||||||
worker.write(self._password_bytes())
|
worker.write(self._password_bytes())
|
||||||
pw_injected = True
|
pw_injected = True
|
||||||
|
|
||||||
if worker.exit_code != 0:
|
if worker.exit_code != 0:
|
||||||
raise DiskError(f"Could not add encryption key {key_file} to {self.luks_dev_path}: {worker.decode()}")
|
raise DiskError(f'Could not add encryption key {key_file} to {self.luks_dev_path}: {worker.decode()}')
|
||||||
|
|
||||||
def _crypttab(
|
def _crypttab(
|
||||||
self,
|
self,
|
||||||
|
|
@ -229,10 +229,10 @@ class Luks2:
|
||||||
key_file: Path,
|
key_file: Path,
|
||||||
options: list[str],
|
options: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
debug(f"Adding crypttab entry for key {key_file}")
|
debug(f'Adding crypttab entry for key {key_file}')
|
||||||
|
|
||||||
with open(crypttab_path, "a") as crypttab:
|
with open(crypttab_path, 'a') as crypttab:
|
||||||
opt = ",".join(options)
|
opt = ','.join(options)
|
||||||
uuid = self._get_luks_uuid()
|
uuid = self._get_luks_uuid()
|
||||||
row = f"{self.mapper_name} UUID={uuid} {key_file} {opt}\n"
|
row = f'{self.mapper_name} UUID={uuid} {key_file} {opt}\n'
|
||||||
crypttab.write(row)
|
crypttab.write(row)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from .abstract_menu import AbstractMenu, AbstractSubMenu
|
||||||
from .list_manager import ListManager
|
from .list_manager import ListManager
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AbstractMenu",
|
'AbstractMenu',
|
||||||
"AbstractSubMenu",
|
'AbstractSubMenu',
|
||||||
"ListManager",
|
'ListManager',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from archinstall.tui.types import Chars, FrameProperties, FrameStyle, PreviewSty
|
||||||
|
|
||||||
from ..output import error
|
from ..output import error
|
||||||
|
|
||||||
CONFIG_KEY = "__config__"
|
CONFIG_KEY = '__config__'
|
||||||
|
|
||||||
|
|
||||||
class AbstractMenu[ValueT]:
|
class AbstractMenu[ValueT]:
|
||||||
|
|
@ -41,7 +41,7 @@ class AbstractMenu[ValueT]:
|
||||||
# TODO: skip processing when it comes from a planified exit
|
# TODO: skip processing when it comes from a planified exit
|
||||||
if len(args) >= 2 and args[1]:
|
if len(args) >= 2 and args[1]:
|
||||||
error(args[1])
|
error(args[1])
|
||||||
Tui.print("Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues")
|
Tui.print('Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues')
|
||||||
raise args[1]
|
raise args[1]
|
||||||
|
|
||||||
self.sync_all_to_config()
|
self.sync_all_to_config()
|
||||||
|
|
@ -82,7 +82,7 @@ class AbstractMenu[ValueT]:
|
||||||
found = True
|
found = True
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
raise ValueError(f"No selector found: {key}")
|
raise ValueError(f'No selector found: {key}')
|
||||||
|
|
||||||
def disable_all(self) -> None:
|
def disable_all(self) -> None:
|
||||||
for item in self._menu_item_group.items:
|
for item in self._menu_item_group.items:
|
||||||
|
|
@ -101,8 +101,8 @@ class AbstractMenu[ValueT]:
|
||||||
allow_reset=self._allow_reset,
|
allow_reset=self._allow_reset,
|
||||||
reset_warning_msg=self._reset_warning,
|
reset_warning_msg=self._reset_warning,
|
||||||
preview_style=PreviewStyle.RIGHT,
|
preview_style=PreviewStyle.RIGHT,
|
||||||
preview_size="auto",
|
preview_size='auto',
|
||||||
preview_frame=FrameProperties("Info", FrameStyle.MAX),
|
preview_frame=FrameProperties('Info', FrameStyle.MAX),
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
match result.type_:
|
match result.type_:
|
||||||
|
|
@ -128,7 +128,7 @@ class AbstractSubMenu[ValueT](AbstractMenu[ValueT]):
|
||||||
auto_cursor: bool = True,
|
auto_cursor: bool = True,
|
||||||
allow_reset: bool = False,
|
allow_reset: bool = False,
|
||||||
):
|
):
|
||||||
back_text = f"{Chars.Right_arrow} " + tr("Back")
|
back_text = f'{Chars.Right_arrow} ' + tr('Back')
|
||||||
item_group.add_item(MenuItem(text=back_text))
|
item_group.add_item(MenuItem(text=back_text))
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@ class ListManager[ValueT]:
|
||||||
|
|
||||||
self._prompt = prompt
|
self._prompt = prompt
|
||||||
|
|
||||||
self._separator = ""
|
self._separator = ''
|
||||||
self._confirm_action = tr("Confirm and exit")
|
self._confirm_action = tr('Confirm and exit')
|
||||||
self._cancel_action = tr("Cancel")
|
self._cancel_action = tr('Cancel')
|
||||||
|
|
||||||
self._terminate_actions = [self._confirm_action, self._cancel_action]
|
self._terminate_actions = [self._confirm_action, self._cancel_action]
|
||||||
self._base_actions = base_actions
|
self._base_actions = base_actions
|
||||||
|
|
@ -66,7 +66,7 @@ class ListManager[ValueT]:
|
||||||
|
|
||||||
prompt = None
|
prompt = None
|
||||||
if self._prompt is not None:
|
if self._prompt is not None:
|
||||||
prompt = f"{self._prompt}\n\n"
|
prompt = f'{self._prompt}\n\n'
|
||||||
|
|
||||||
prompt = None
|
prompt = None
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ class ListManager[ValueT]:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
value = result.get_value()
|
value = result.get_value()
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
if value in self._base_actions:
|
if value in self._base_actions:
|
||||||
value = cast(str, value)
|
value = cast(str, value)
|
||||||
|
|
@ -108,7 +108,7 @@ class ListManager[ValueT]:
|
||||||
items = [MenuItem(o, value=o) for o in options]
|
items = [MenuItem(o, value=o) for o in options]
|
||||||
group = MenuItemGroup(items, sort_items=False)
|
group = MenuItemGroup(items, sort_items=False)
|
||||||
|
|
||||||
header = f"{self.selected_action_display(entry)}\n"
|
header = f'{self.selected_action_display(entry)}\n'
|
||||||
|
|
||||||
result = SelectMenu[str](
|
result = SelectMenu[str](
|
||||||
group,
|
group,
|
||||||
|
|
@ -122,7 +122,7 @@ class ListManager[ValueT]:
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
value = result.get_value()
|
value = result.get_value()
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
if value != self._cancel_action:
|
if value != self._cancel_action:
|
||||||
self._data = self.handle_action(value, entry, self._data)
|
self._data = self.handle_action(value, entry, self._data)
|
||||||
|
|
@ -132,14 +132,14 @@ class ListManager[ValueT]:
|
||||||
this will return the value to be displayed in the
|
this will return the value to be displayed in the
|
||||||
"Select an action for '{}'" string
|
"Select an action for '{}'" string
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Please implement me in the child class")
|
raise NotImplementedError('Please implement me in the child class')
|
||||||
|
|
||||||
def handle_action(self, action: str, entry: ValueT | None, data: list[ValueT]) -> list[ValueT]:
|
def handle_action(self, action: str, entry: ValueT | None, data: list[ValueT]) -> list[ValueT]:
|
||||||
"""
|
"""
|
||||||
this function is called when a base action or
|
this function is called when a base action or
|
||||||
a specific action for an entry is triggered
|
a specific action for an entry is triggered
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Please implement me in the child class")
|
raise NotImplementedError('Please implement me in the child class')
|
||||||
|
|
||||||
def filter_options(self, selection: ValueT, options: list[str]) -> list[str]:
|
def filter_options(self, selection: ValueT, options: list[str]) -> list[str]:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class MenuHelper:
|
||||||
data: list[Any],
|
data: list[Any],
|
||||||
additional_options: list[str] = [],
|
additional_options: list[str] = [],
|
||||||
) -> None:
|
) -> None:
|
||||||
self._separator = ""
|
self._separator = ''
|
||||||
self._data = data
|
self._data = data
|
||||||
self._additional_options = additional_options
|
self._additional_options = additional_options
|
||||||
|
|
||||||
|
|
@ -39,10 +39,10 @@ class MenuHelper:
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
table = FormattedOutput.as_table(data)
|
table = FormattedOutput.as_table(data)
|
||||||
rows = table.split("\n")
|
rows = table.split('\n')
|
||||||
|
|
||||||
# these are the header rows of the table
|
# these are the header rows of the table
|
||||||
display_data = {f"{rows[0]}": None, f"{rows[1]}": None}
|
display_data = {f'{rows[0]}': None, f'{rows[1]}': None}
|
||||||
|
|
||||||
for row, entry in zip(rows[2:], data):
|
for row, entry in zip(rows[2:], data):
|
||||||
display_data[row] = entry
|
display_data[row] = entry
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,16 @@ from .output import FormattedOutput, debug
|
||||||
class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
||||||
def __init__(self, custom_repositories: list[CustomRepository]):
|
def __init__(self, custom_repositories: list[CustomRepository]):
|
||||||
self._actions = [
|
self._actions = [
|
||||||
tr("Add a custom repository"),
|
tr('Add a custom repository'),
|
||||||
tr("Change custom repository"),
|
tr('Change custom repository'),
|
||||||
tr("Delete custom repository"),
|
tr('Delete custom repository'),
|
||||||
]
|
]
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
custom_repositories,
|
custom_repositories,
|
||||||
[self._actions[0]],
|
[self._actions[0]],
|
||||||
self._actions[1:],
|
self._actions[1:],
|
||||||
"",
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -69,7 +69,7 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
||||||
|
|
||||||
def _add_custom_repository(self, preset: CustomRepository | None = None) -> CustomRepository | None:
|
def _add_custom_repository(self, preset: CustomRepository | None = None) -> CustomRepository | None:
|
||||||
edit_result = EditMenu(
|
edit_result = EditMenu(
|
||||||
tr("Repository name"),
|
tr('Repository name'),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
default_text=preset.name if preset else None,
|
default_text=preset.name if preset else None,
|
||||||
|
|
@ -81,12 +81,12 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
||||||
case ResultType.Skip:
|
case ResultType.Skip:
|
||||||
return preset
|
return preset
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
header = f"{tr('Name')}: {name}"
|
header = f'{tr("Name")}: {name}'
|
||||||
|
|
||||||
edit_result = EditMenu(
|
edit_result = EditMenu(
|
||||||
tr("Url"),
|
tr('Url'),
|
||||||
header=header,
|
header=header,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
|
|
@ -99,10 +99,10 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
||||||
case ResultType.Skip:
|
case ResultType.Skip:
|
||||||
return preset
|
return preset
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
header += f"\n{tr('Url')}: {url}\n"
|
header += f'\n{tr("Url")}: {url}\n'
|
||||||
prompt = f"{header}\n" + tr("Select signature check")
|
prompt = f'{header}\n' + tr('Select signature check')
|
||||||
|
|
||||||
sign_chk_items = [MenuItem(s.value, value=s.value) for s in SignCheck]
|
sign_chk_items = [MenuItem(s.value, value=s.value) for s in SignCheck]
|
||||||
group = MenuItemGroup(sign_chk_items, sort_items=False)
|
group = MenuItemGroup(sign_chk_items, sort_items=False)
|
||||||
|
|
@ -121,10 +121,10 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
sign_check = SignCheck(result.get_value())
|
sign_check = SignCheck(result.get_value())
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
header += f"{tr('Signature check')}: {sign_check.value}\n"
|
header += f'{tr("Signature check")}: {sign_check.value}\n'
|
||||||
prompt = f"{header}\n" + "Select signature option"
|
prompt = f'{header}\n' + 'Select signature option'
|
||||||
|
|
||||||
sign_opt_items = [MenuItem(s.value, value=s.value) for s in SignOption]
|
sign_opt_items = [MenuItem(s.value, value=s.value) for s in SignOption]
|
||||||
group = MenuItemGroup(sign_opt_items, sort_items=False)
|
group = MenuItemGroup(sign_opt_items, sort_items=False)
|
||||||
|
|
@ -143,7 +143,7 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
sign_opt = SignOption(result.get_value())
|
sign_opt = SignOption(result.get_value())
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Unhandled return type")
|
raise ValueError('Unhandled return type')
|
||||||
|
|
||||||
return CustomRepository(name, url, sign_check, sign_opt)
|
return CustomRepository(name, url, sign_check, sign_opt)
|
||||||
|
|
||||||
|
|
@ -151,16 +151,16 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
||||||
class CustomMirrorServersList(ListManager[CustomServer]):
|
class CustomMirrorServersList(ListManager[CustomServer]):
|
||||||
def __init__(self, custom_servers: list[CustomServer]):
|
def __init__(self, custom_servers: list[CustomServer]):
|
||||||
self._actions = [
|
self._actions = [
|
||||||
tr("Add a custom server"),
|
tr('Add a custom server'),
|
||||||
tr("Change custom server"),
|
tr('Change custom server'),
|
||||||
tr("Delete custom server"),
|
tr('Delete custom server'),
|
||||||
]
|
]
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
custom_servers,
|
custom_servers,
|
||||||
[self._actions[0]],
|
[self._actions[0]],
|
||||||
self._actions[1:],
|
self._actions[1:],
|
||||||
"",
|
'',
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -191,7 +191,7 @@ class CustomMirrorServersList(ListManager[CustomServer]):
|
||||||
|
|
||||||
def _add_custom_server(self, preset: CustomServer | None = None) -> CustomServer | None:
|
def _add_custom_server(self, preset: CustomServer | None = None) -> CustomServer | None:
|
||||||
edit_result = EditMenu(
|
edit_result = EditMenu(
|
||||||
tr("Server url"),
|
tr('Server url'),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
default_text=preset.url if preset else None,
|
default_text=preset.url if preset else None,
|
||||||
|
|
@ -229,54 +229,54 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
|
||||||
def _define_menu_options(self) -> list[MenuItem]:
|
def _define_menu_options(self) -> list[MenuItem]:
|
||||||
return [
|
return [
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Select regions"),
|
text=tr('Select regions'),
|
||||||
action=select_mirror_regions,
|
action=select_mirror_regions,
|
||||||
value=self._mirror_config.mirror_regions,
|
value=self._mirror_config.mirror_regions,
|
||||||
preview_action=self._prev_regions,
|
preview_action=self._prev_regions,
|
||||||
key="mirror_regions",
|
key='mirror_regions',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Add custom servers"),
|
text=tr('Add custom servers'),
|
||||||
action=add_custom_mirror_servers,
|
action=add_custom_mirror_servers,
|
||||||
value=self._mirror_config.custom_servers,
|
value=self._mirror_config.custom_servers,
|
||||||
preview_action=self._prev_custom_servers,
|
preview_action=self._prev_custom_servers,
|
||||||
key="custom_servers",
|
key='custom_servers',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Optional repositories"),
|
text=tr('Optional repositories'),
|
||||||
action=select_optional_repositories,
|
action=select_optional_repositories,
|
||||||
value=[],
|
value=[],
|
||||||
preview_action=self._prev_additional_repos,
|
preview_action=self._prev_additional_repos,
|
||||||
key="optional_repositories",
|
key='optional_repositories',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Add custom repository"),
|
text=tr('Add custom repository'),
|
||||||
action=select_custom_mirror,
|
action=select_custom_mirror,
|
||||||
value=self._mirror_config.custom_repositories,
|
value=self._mirror_config.custom_repositories,
|
||||||
preview_action=self._prev_custom_mirror,
|
preview_action=self._prev_custom_mirror,
|
||||||
key="custom_repositories",
|
key='custom_repositories',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def _prev_regions(self, item: MenuItem) -> str | None:
|
def _prev_regions(self, item: MenuItem) -> str | None:
|
||||||
regions = item.get_value()
|
regions = item.get_value()
|
||||||
|
|
||||||
output = ""
|
output = ''
|
||||||
for region in regions:
|
for region in regions:
|
||||||
output += f"{region.name}\n"
|
output += f'{region.name}\n'
|
||||||
|
|
||||||
for url in region.urls:
|
for url in region.urls:
|
||||||
output += f" - {url}\n"
|
output += f' - {url}\n'
|
||||||
|
|
||||||
output += "\n"
|
output += '\n'
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def _prev_additional_repos(self, item: MenuItem) -> str | None:
|
def _prev_additional_repos(self, item: MenuItem) -> str | None:
|
||||||
if item.value:
|
if item.value:
|
||||||
repositories: list[Repository] = item.value
|
repositories: list[Repository] = item.value
|
||||||
repos = ", ".join([repo.value for repo in repositories])
|
repos = ', '.join([repo.value for repo in repositories])
|
||||||
return f"{tr('Additional repositories')}: {repos}"
|
return f'{tr("Additional repositories")}: {repos}'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_custom_mirror(self, item: MenuItem) -> str | None:
|
def _prev_custom_mirror(self, item: MenuItem) -> str | None:
|
||||||
|
|
@ -292,7 +292,7 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
custom_servers: list[CustomServer] = item.value
|
custom_servers: list[CustomServer] = item.value
|
||||||
output = "\n".join([server.url for server in custom_servers])
|
output = '\n'.join([server.url for server in custom_servers])
|
||||||
return output.strip()
|
return output.strip()
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -302,7 +302,7 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
|
||||||
|
|
||||||
|
|
||||||
def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]:
|
def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]:
|
||||||
Tui.print(tr("Loading mirror regions..."), clear_screen=True)
|
Tui.print(tr('Loading mirror regions...'), clear_screen=True)
|
||||||
|
|
||||||
mirror_list_handler.load_mirrors()
|
mirror_list_handler.load_mirrors()
|
||||||
available_regions = mirror_list_handler.get_mirror_regions()
|
available_regions = mirror_list_handler.get_mirror_regions()
|
||||||
|
|
@ -320,7 +320,7 @@ def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]:
|
||||||
result = SelectMenu[MirrorRegion](
|
result = SelectMenu[MirrorRegion](
|
||||||
group,
|
group,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Mirror regions")),
|
frame=FrameProperties.min(tr('Mirror regions')),
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
multi=True,
|
multi=True,
|
||||||
|
|
@ -362,7 +362,7 @@ def select_optional_repositories(preset: list[Repository]) -> list[Repository]:
|
||||||
result = SelectMenu[Repository](
|
result = SelectMenu[Repository](
|
||||||
group,
|
group,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min("Additional repositories"),
|
frame=FrameProperties.min('Additional repositories'),
|
||||||
allow_reset=True,
|
allow_reset=True,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
multi=True,
|
multi=True,
|
||||||
|
|
@ -380,7 +380,7 @@ def select_optional_repositories(preset: list[Repository]) -> list[Repository]:
|
||||||
class MirrorListHandler:
|
class MirrorListHandler:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
local_mirrorlist: Path = Path("/etc/pacman.d/mirrorlist"),
|
local_mirrorlist: Path = Path('/etc/pacman.d/mirrorlist'),
|
||||||
) -> None:
|
) -> None:
|
||||||
self._local_mirrorlist = local_mirrorlist
|
self._local_mirrorlist = local_mirrorlist
|
||||||
self._status_mappings: dict[str, list[MirrorStatusEntryV3]] | None = None
|
self._status_mappings: dict[str, list[MirrorStatusEntryV3]] | None = None
|
||||||
|
|
@ -413,7 +413,7 @@ class MirrorListHandler:
|
||||||
self.load_local_mirrors()
|
self.load_local_mirrors()
|
||||||
|
|
||||||
def load_remote_mirrors(self) -> bool:
|
def load_remote_mirrors(self) -> bool:
|
||||||
url = "https://archlinux.org/mirrors/status/json/"
|
url = 'https://archlinux.org/mirrors/status/json/'
|
||||||
attempts = 3
|
attempts = 3
|
||||||
|
|
||||||
for attempt_nr in range(attempts):
|
for attempt_nr in range(attempts):
|
||||||
|
|
@ -422,14 +422,14 @@ class MirrorListHandler:
|
||||||
self._status_mappings = self._parse_remote_mirror_list(mirrorlist)
|
self._status_mappings = self._parse_remote_mirror_list(mirrorlist)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug(f"Error while fetching mirror list: {e}")
|
debug(f'Error while fetching mirror list: {e}')
|
||||||
time.sleep(attempt_nr + 1)
|
time.sleep(attempt_nr + 1)
|
||||||
|
|
||||||
debug("Unable to fetch mirror list remotely, falling back to local mirror list")
|
debug('Unable to fetch mirror list remotely, falling back to local mirror list')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def load_local_mirrors(self) -> None:
|
def load_local_mirrors(self) -> None:
|
||||||
with self._local_mirrorlist.open("r") as fp:
|
with self._local_mirrorlist.open('r') as fp:
|
||||||
mirrorlist = fp.read()
|
mirrorlist = fp.read()
|
||||||
self._status_mappings = self._parse_locale_mirrors(mirrorlist)
|
self._status_mappings = self._parse_locale_mirrors(mirrorlist)
|
||||||
|
|
||||||
|
|
@ -456,14 +456,14 @@ class MirrorListHandler:
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if mirror.country == "":
|
if mirror.country == '':
|
||||||
# TODO: This should be removed once RFC!29 is merged and completed
|
# TODO: This should be removed once RFC!29 is merged and completed
|
||||||
# Until then, there are mirrors which lacks data in the backend
|
# Until then, there are mirrors which lacks data in the backend
|
||||||
# and there is no way of knowing where they're located.
|
# and there is no way of knowing where they're located.
|
||||||
# So we have to assume world-wide
|
# So we have to assume world-wide
|
||||||
mirror.country = "Worldwide"
|
mirror.country = 'Worldwide'
|
||||||
|
|
||||||
if mirror.url.startswith("http"):
|
if mirror.url.startswith('http'):
|
||||||
sorting_placeholder.setdefault(mirror.country, []).append(mirror)
|
sorting_placeholder.setdefault(mirror.country, []).append(mirror)
|
||||||
|
|
||||||
sorted_by_regions: dict[str, list[MirrorStatusEntryV3]] = dict(
|
sorted_by_regions: dict[str, list[MirrorStatusEntryV3]] = dict(
|
||||||
|
|
@ -480,35 +480,35 @@ class MirrorListHandler:
|
||||||
|
|
||||||
mirror_list: dict[str, list[MirrorStatusEntryV3]] = {}
|
mirror_list: dict[str, list[MirrorStatusEntryV3]] = {}
|
||||||
|
|
||||||
current_region = ""
|
current_region = ''
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
|
||||||
if line.startswith("## "):
|
if line.startswith('## '):
|
||||||
current_region = line.replace("## ", "").strip()
|
current_region = line.replace('## ', '').strip()
|
||||||
mirror_list.setdefault(current_region, [])
|
mirror_list.setdefault(current_region, [])
|
||||||
|
|
||||||
if line.startswith("Server = "):
|
if line.startswith('Server = '):
|
||||||
if not current_region:
|
if not current_region:
|
||||||
current_region = "Local"
|
current_region = 'Local'
|
||||||
mirror_list.setdefault(current_region, [])
|
mirror_list.setdefault(current_region, [])
|
||||||
|
|
||||||
url = line.removeprefix("Server = ")
|
url = line.removeprefix('Server = ')
|
||||||
|
|
||||||
mirror_entry = MirrorStatusEntryV3(
|
mirror_entry = MirrorStatusEntryV3(
|
||||||
url=url.removesuffix("$repo/os/$arch"),
|
url=url.removesuffix('$repo/os/$arch'),
|
||||||
protocol=urllib.parse.urlparse(url).scheme,
|
protocol=urllib.parse.urlparse(url).scheme,
|
||||||
active=True,
|
active=True,
|
||||||
country=current_region or "Worldwide",
|
country=current_region or 'Worldwide',
|
||||||
# The following values are normally populated by
|
# The following values are normally populated by
|
||||||
# archlinux.org mirror-list endpoint, and can't be known
|
# archlinux.org mirror-list endpoint, and can't be known
|
||||||
# from just the local mirror-list file.
|
# from just the local mirror-list file.
|
||||||
country_code="WW",
|
country_code='WW',
|
||||||
isos=True,
|
isos=True,
|
||||||
ipv4=True,
|
ipv4=True,
|
||||||
ipv6=True,
|
ipv6=True,
|
||||||
details="Locally defined mirror",
|
details='Locally defined mirror',
|
||||||
)
|
)
|
||||||
|
|
||||||
mirror_list[current_region].append(mirror_entry)
|
mirror_list[current_region].append(mirror_entry)
|
||||||
|
|
|
||||||
|
|
@ -35,46 +35,46 @@ from .profile_model import ProfileConfiguration
|
||||||
from .users import PasswordStrength, User
|
from .users import PasswordStrength, User
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Audio",
|
'Audio',
|
||||||
"AudioConfiguration",
|
'AudioConfiguration',
|
||||||
"BDevice",
|
'BDevice',
|
||||||
"Bootloader",
|
'Bootloader',
|
||||||
"CustomRepository",
|
'CustomRepository',
|
||||||
"DeviceGeometry",
|
'DeviceGeometry',
|
||||||
"DeviceModification",
|
'DeviceModification',
|
||||||
"DiskEncryption",
|
'DiskEncryption',
|
||||||
"DiskLayoutConfiguration",
|
'DiskLayoutConfiguration',
|
||||||
"DiskLayoutType",
|
'DiskLayoutType',
|
||||||
"EncryptionType",
|
'EncryptionType',
|
||||||
"Fido2Device",
|
'Fido2Device',
|
||||||
"FilesystemType",
|
'FilesystemType',
|
||||||
"LocalPackage",
|
'LocalPackage',
|
||||||
"LocaleConfiguration",
|
'LocaleConfiguration',
|
||||||
"LsblkInfo",
|
'LsblkInfo',
|
||||||
"LvmConfiguration",
|
'LvmConfiguration',
|
||||||
"LvmLayoutType",
|
'LvmLayoutType',
|
||||||
"LvmVolume",
|
'LvmVolume',
|
||||||
"LvmVolumeGroup",
|
'LvmVolumeGroup',
|
||||||
"LvmVolumeStatus",
|
'LvmVolumeStatus',
|
||||||
"MirrorConfiguration",
|
'MirrorConfiguration',
|
||||||
"MirrorRegion",
|
'MirrorRegion',
|
||||||
"ModificationStatus",
|
'ModificationStatus',
|
||||||
"NetworkConfiguration",
|
'NetworkConfiguration',
|
||||||
"Nic",
|
'Nic',
|
||||||
"NicType",
|
'NicType',
|
||||||
"PackageSearch",
|
'PackageSearch',
|
||||||
"PackageSearchResult",
|
'PackageSearchResult',
|
||||||
"PartitionFlag",
|
'PartitionFlag',
|
||||||
"PartitionModification",
|
'PartitionModification',
|
||||||
"PartitionTable",
|
'PartitionTable',
|
||||||
"PartitionType",
|
'PartitionType',
|
||||||
"PasswordStrength",
|
'PasswordStrength',
|
||||||
"ProfileConfiguration",
|
'ProfileConfiguration',
|
||||||
"Repository",
|
'Repository',
|
||||||
"SectorSize",
|
'SectorSize',
|
||||||
"Size",
|
'Size',
|
||||||
"SubvolumeModification",
|
'SubvolumeModification',
|
||||||
"Unit",
|
'Unit',
|
||||||
"User",
|
'User',
|
||||||
"_DeviceInfo",
|
'_DeviceInfo',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Audio(StrEnum):
|
class Audio(StrEnum):
|
||||||
NO_AUDIO = "No audio server"
|
NO_AUDIO = 'No audio server'
|
||||||
PIPEWIRE = auto()
|
PIPEWIRE = auto()
|
||||||
PULSEAUDIO = auto()
|
PULSEAUDIO = auto()
|
||||||
|
|
||||||
|
|
@ -21,20 +21,20 @@ class AudioConfiguration:
|
||||||
|
|
||||||
def json(self) -> dict[str, str]:
|
def json(self) -> dict[str, str]:
|
||||||
return {
|
return {
|
||||||
"audio": self.audio.value,
|
'audio': self.audio.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_arg(arg: dict[str, str]) -> "AudioConfiguration":
|
def parse_arg(arg: dict[str, str]) -> 'AudioConfiguration':
|
||||||
return AudioConfiguration(
|
return AudioConfiguration(
|
||||||
Audio(arg["audio"]),
|
Audio(arg['audio']),
|
||||||
)
|
)
|
||||||
|
|
||||||
def install_audio_config(
|
def install_audio_config(
|
||||||
self,
|
self,
|
||||||
installation: "Installer",
|
installation: 'Installer',
|
||||||
) -> None:
|
) -> None:
|
||||||
info(f"Installing audio server: {self.audio.name}")
|
info(f'Installing audio server: {self.audio.name}')
|
||||||
|
|
||||||
from ...default_profiles.applications.pipewire import PipewireProfile
|
from ...default_profiles.applications.pipewire import PipewireProfile
|
||||||
|
|
||||||
|
|
@ -42,11 +42,11 @@ class AudioConfiguration:
|
||||||
case Audio.PIPEWIRE:
|
case Audio.PIPEWIRE:
|
||||||
PipewireProfile().install(installation)
|
PipewireProfile().install(installation)
|
||||||
case Audio.PULSEAUDIO:
|
case Audio.PULSEAUDIO:
|
||||||
installation.add_additional_packages("pulseaudio")
|
installation.add_additional_packages('pulseaudio')
|
||||||
|
|
||||||
if self.audio != Audio.NO_AUDIO:
|
if self.audio != Audio.NO_AUDIO:
|
||||||
if SysInfo.requires_sof_fw():
|
if SysInfo.requires_sof_fw():
|
||||||
installation.add_additional_packages("sof-firmware")
|
installation.add_additional_packages('sof-firmware')
|
||||||
|
|
||||||
if SysInfo.requires_alsa_fw():
|
if SysInfo.requires_alsa_fw():
|
||||||
installation.add_additional_packages("alsa-firmware")
|
installation.add_additional_packages('alsa-firmware')
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ from ..output import warn
|
||||||
|
|
||||||
|
|
||||||
class Bootloader(Enum):
|
class Bootloader(Enum):
|
||||||
Systemd = "Systemd-boot"
|
Systemd = 'Systemd-boot'
|
||||||
Grub = "Grub"
|
Grub = 'Grub'
|
||||||
Efistub = "Efistub"
|
Efistub = 'Efistub'
|
||||||
Limine = "Limine"
|
Limine = 'Limine'
|
||||||
|
|
||||||
def has_uki_support(self) -> bool:
|
def has_uki_support(self) -> bool:
|
||||||
match self:
|
match self:
|
||||||
|
|
@ -40,7 +40,7 @@ class Bootloader(Enum):
|
||||||
bootloader = bootloader.capitalize()
|
bootloader = bootloader.capitalize()
|
||||||
|
|
||||||
if bootloader not in cls.values():
|
if bootloader not in cls.values():
|
||||||
values = ", ".join(cls.values())
|
values = ', '.join(cls.values())
|
||||||
warn(f'Invalid bootloader value "{bootloader}". Allowed values: {values}')
|
warn(f'Invalid bootloader value "{bootloader}". Allowed values: {values}')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return Bootloader(bootloader)
|
return Bootloader(bootloader)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -13,42 +13,42 @@ class LocaleConfiguration:
|
||||||
sys_enc: str
|
sys_enc: str
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default() -> "LocaleConfiguration":
|
def default() -> 'LocaleConfiguration':
|
||||||
layout = get_kb_layout()
|
layout = get_kb_layout()
|
||||||
if layout == "":
|
if layout == '':
|
||||||
layout = "us"
|
layout = 'us'
|
||||||
return LocaleConfiguration(layout, "en_US.UTF-8", "UTF-8")
|
return LocaleConfiguration(layout, 'en_US.UTF-8', 'UTF-8')
|
||||||
|
|
||||||
def json(self) -> dict[str, str]:
|
def json(self) -> dict[str, str]:
|
||||||
return {
|
return {
|
||||||
"kb_layout": self.kb_layout,
|
'kb_layout': self.kb_layout,
|
||||||
"sys_lang": self.sys_lang,
|
'sys_lang': self.sys_lang,
|
||||||
"sys_enc": self.sys_enc,
|
'sys_enc': self.sys_enc,
|
||||||
}
|
}
|
||||||
|
|
||||||
def preview(self) -> str:
|
def preview(self) -> str:
|
||||||
output = "{}: {}\n".format(tr("Keyboard layout"), self.kb_layout)
|
output = '{}: {}\n'.format(tr('Keyboard layout'), self.kb_layout)
|
||||||
output += "{}: {}\n".format(tr("Locale language"), self.sys_lang)
|
output += '{}: {}\n'.format(tr('Locale language'), self.sys_lang)
|
||||||
output += "{}: {}".format(tr("Locale encoding"), self.sys_enc)
|
output += '{}: {}'.format(tr('Locale encoding'), self.sys_enc)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _load_config(cls, config: "LocaleConfiguration", args: dict[str, str]) -> "LocaleConfiguration":
|
def _load_config(cls, config: 'LocaleConfiguration', args: dict[str, str]) -> 'LocaleConfiguration':
|
||||||
if "sys_lang" in args:
|
if 'sys_lang' in args:
|
||||||
config.sys_lang = args["sys_lang"]
|
config.sys_lang = args['sys_lang']
|
||||||
if "sys_enc" in args:
|
if 'sys_enc' in args:
|
||||||
config.sys_enc = args["sys_enc"]
|
config.sys_enc = args['sys_enc']
|
||||||
if "kb_layout" in args:
|
if 'kb_layout' in args:
|
||||||
config.kb_layout = args["kb_layout"]
|
config.kb_layout = args['kb_layout']
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_arg(cls, args: dict[str, Any]) -> "LocaleConfiguration":
|
def parse_arg(cls, args: dict[str, Any]) -> 'LocaleConfiguration':
|
||||||
default = cls.default()
|
default = cls.default()
|
||||||
|
|
||||||
if "locale_config" in args:
|
if 'locale_config' in args:
|
||||||
default = cls._load_config(default, args["locale_config"])
|
default = cls._load_config(default, args['locale_config'])
|
||||||
else:
|
else:
|
||||||
default = cls._load_config(default, args)
|
default = cls._load_config(default, args)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class MirrorStatusEntryV3(BaseModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def server_url(self) -> str:
|
def server_url(self) -> str:
|
||||||
return f"{self.url}$repo/os/$arch"
|
return f'{self.url}$repo/os/$arch'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speed(self) -> float:
|
def speed(self) -> float:
|
||||||
|
|
@ -50,8 +50,8 @@ class MirrorStatusEntryV3(BaseModel):
|
||||||
|
|
||||||
retry = 0
|
retry = 0
|
||||||
while retry < self._speedtest_retries and self._speed is None:
|
while retry < self._speedtest_retries and self._speed is None:
|
||||||
debug(f"Checking download speed of {self._hostname}[{self.score}] by fetching: {self.url}core/os/x86_64/core.db")
|
debug(f'Checking download speed of {self._hostname}[{self.score}] by fetching: {self.url}core/os/x86_64/core.db')
|
||||||
req = urllib.request.Request(url=f"{self.url}core/os/x86_64/core.db")
|
req = urllib.request.Request(url=f'{self.url}core/os/x86_64/core.db')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req, None, 5) as handle, DownloadTimer(timeout=5) as timer:
|
with urllib.request.urlopen(req, None, 5) as handle, DownloadTimer(timeout=5) as timer:
|
||||||
|
|
@ -59,17 +59,17 @@ class MirrorStatusEntryV3(BaseModel):
|
||||||
|
|
||||||
assert timer.time is not None
|
assert timer.time is not None
|
||||||
self._speed = size / timer.time
|
self._speed = size / timer.time
|
||||||
debug(f" speed: {self._speed} ({int(self._speed / 1024 / 1024 * 100) / 100}MiB/s)")
|
debug(f' speed: {self._speed} ({int(self._speed / 1024 / 1024 * 100) / 100}MiB/s)')
|
||||||
# Do not retry error
|
# Do not retry error
|
||||||
except urllib.error.URLError as error:
|
except urllib.error.URLError as error:
|
||||||
debug(f" speed: <undetermined> ({error}), skip")
|
debug(f' speed: <undetermined> ({error}), skip')
|
||||||
self._speed = 0
|
self._speed = 0
|
||||||
# Do retry error
|
# Do retry error
|
||||||
except (http.client.IncompleteRead, ConnectionResetError) as error:
|
except (http.client.IncompleteRead, ConnectionResetError) as error:
|
||||||
debug(f" speed: <undetermined> ({error}), retry")
|
debug(f' speed: <undetermined> ({error}), retry')
|
||||||
# Catch all
|
# Catch all
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
debug(f" speed: <undetermined> ({error}), skip")
|
debug(f' speed: <undetermined> ({error}), skip')
|
||||||
self._speed = 0
|
self._speed = 0
|
||||||
|
|
||||||
retry += 1
|
retry += 1
|
||||||
|
|
@ -87,27 +87,27 @@ class MirrorStatusEntryV3(BaseModel):
|
||||||
We do this because some hosts blocks ICMP so we'll have to rely on .speed() instead which is slower.
|
We do this because some hosts blocks ICMP so we'll have to rely on .speed() instead which is slower.
|
||||||
"""
|
"""
|
||||||
if self._latency is None:
|
if self._latency is None:
|
||||||
debug(f"Checking latency for {self.url}")
|
debug(f'Checking latency for {self.url}')
|
||||||
self._latency = ping(self._hostname, timeout=2)
|
self._latency = ping(self._hostname, timeout=2)
|
||||||
debug(f" latency: {self._latency}")
|
debug(f' latency: {self._latency}')
|
||||||
|
|
||||||
return self._latency
|
return self._latency
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@field_validator("score", mode="before")
|
@field_validator('score', mode='before')
|
||||||
def validate_score(cls, value: float) -> int | None:
|
def validate_score(cls, value: float) -> int | None:
|
||||||
if value is not None:
|
if value is not None:
|
||||||
value = round(value)
|
value = round(value)
|
||||||
debug(f" score: {value}")
|
debug(f' score: {value}')
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode='after')
|
||||||
def debug_output(self, validation_info) -> "MirrorStatusEntryV3":
|
def debug_output(self, validation_info) -> 'MirrorStatusEntryV3':
|
||||||
self._hostname, *port = urllib.parse.urlparse(self.url).netloc.split(":", 1)
|
self._hostname, *port = urllib.parse.urlparse(self.url).netloc.split(':', 1)
|
||||||
self._port = int(port[0]) if port and len(port) >= 1 else None
|
self._port = int(port[0]) if port and len(port) >= 1 else None
|
||||||
|
|
||||||
debug(f"Loaded mirror {self._hostname}" + (f" with current score of {self.score}" if self.score else ""))
|
debug(f'Loaded mirror {self._hostname}' + (f' with current score of {self.score}' if self.score else ''))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -118,16 +118,16 @@ class MirrorStatusListV3(BaseModel):
|
||||||
urls: list[MirrorStatusEntryV3]
|
urls: list[MirrorStatusEntryV3]
|
||||||
version: int
|
version: int
|
||||||
|
|
||||||
@model_validator(mode="before")
|
@model_validator(mode='before')
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_model(
|
def check_model(
|
||||||
cls,
|
cls,
|
||||||
data: dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]],
|
data: dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]],
|
||||||
) -> dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]]:
|
) -> dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]]:
|
||||||
if data.get("version") == 3:
|
if data.get('version') == 3:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
raise ValueError("MirrorStatusListV3 only accepts version 3 data from https://archlinux.org/mirrors/status/json/")
|
raise ValueError('MirrorStatusListV3 only accepts version 3 data from https://archlinux.org/mirrors/status/json/')
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -146,14 +146,14 @@ class MirrorRegion:
|
||||||
|
|
||||||
|
|
||||||
class SignCheck(Enum):
|
class SignCheck(Enum):
|
||||||
Never = "Never"
|
Never = 'Never'
|
||||||
Optional = "Optional"
|
Optional = 'Optional'
|
||||||
Required = "Required"
|
Required = 'Required'
|
||||||
|
|
||||||
|
|
||||||
class SignOption(Enum):
|
class SignOption(Enum):
|
||||||
TrustedOnly = "TrustedOnly"
|
TrustedOnly = 'TrustedOnly'
|
||||||
TrustAll = "TrustAll"
|
TrustAll = 'TrustAll'
|
||||||
|
|
||||||
|
|
||||||
class _CustomRepositorySerialization(TypedDict):
|
class _CustomRepositorySerialization(TypedDict):
|
||||||
|
|
@ -172,30 +172,30 @@ class CustomRepository:
|
||||||
|
|
||||||
def table_data(self) -> dict[str, str]:
|
def table_data(self) -> dict[str, str]:
|
||||||
return {
|
return {
|
||||||
"Name": self.name,
|
'Name': self.name,
|
||||||
"Url": self.url,
|
'Url': self.url,
|
||||||
"Sign check": self.sign_check.value,
|
'Sign check': self.sign_check.value,
|
||||||
"Sign options": self.sign_option.value,
|
'Sign options': self.sign_option.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
def json(self) -> _CustomRepositorySerialization:
|
def json(self) -> _CustomRepositorySerialization:
|
||||||
return {
|
return {
|
||||||
"name": self.name,
|
'name': self.name,
|
||||||
"url": self.url,
|
'url': self.url,
|
||||||
"sign_check": self.sign_check.value,
|
'sign_check': self.sign_check.value,
|
||||||
"sign_option": self.sign_option.value,
|
'sign_option': self.sign_option.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_args(cls, args: list[dict[str, str]]) -> list["CustomRepository"]:
|
def parse_args(cls, args: list[dict[str, str]]) -> list['CustomRepository']:
|
||||||
configs = []
|
configs = []
|
||||||
for arg in args:
|
for arg in args:
|
||||||
configs.append(
|
configs.append(
|
||||||
CustomRepository(
|
CustomRepository(
|
||||||
arg["name"],
|
arg['name'],
|
||||||
arg["url"],
|
arg['url'],
|
||||||
SignCheck(arg["sign_check"]),
|
SignCheck(arg['sign_check']),
|
||||||
SignOption(arg["sign_option"]),
|
SignOption(arg['sign_option']),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -207,17 +207,17 @@ class CustomServer:
|
||||||
url: str
|
url: str
|
||||||
|
|
||||||
def table_data(self) -> dict[str, str]:
|
def table_data(self) -> dict[str, str]:
|
||||||
return {"Url": self.url}
|
return {'Url': self.url}
|
||||||
|
|
||||||
def json(self) -> dict[str, str]:
|
def json(self) -> dict[str, str]:
|
||||||
return {"url": self.url}
|
return {'url': self.url}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_args(cls, args: list[dict[str, str]]) -> list["CustomServer"]:
|
def parse_args(cls, args: list[dict[str, str]]) -> list['CustomServer']:
|
||||||
configs = []
|
configs = []
|
||||||
for arg in args:
|
for arg in args:
|
||||||
configs.append(
|
configs.append(
|
||||||
CustomServer(arg["url"]),
|
CustomServer(arg['url']),
|
||||||
)
|
)
|
||||||
|
|
||||||
return configs
|
return configs
|
||||||
|
|
@ -239,11 +239,11 @@ class MirrorConfiguration:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def region_names(self) -> str:
|
def region_names(self) -> str:
|
||||||
return "\n".join([m.name for m in self.mirror_regions])
|
return '\n'.join([m.name for m in self.mirror_regions])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def custom_server_urls(self) -> str:
|
def custom_server_urls(self) -> str:
|
||||||
return "\n".join([s.url for s in self.custom_servers])
|
return '\n'.join([s.url for s in self.custom_servers])
|
||||||
|
|
||||||
def json(self) -> _MirrorConfigurationSerialization:
|
def json(self) -> _MirrorConfigurationSerialization:
|
||||||
regions = {}
|
regions = {}
|
||||||
|
|
@ -251,26 +251,26 @@ class MirrorConfiguration:
|
||||||
regions.update(m.json())
|
regions.update(m.json())
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"mirror_regions": regions,
|
'mirror_regions': regions,
|
||||||
"custom_servers": self.custom_servers,
|
'custom_servers': self.custom_servers,
|
||||||
"optional_repositories": [r.value for r in self.optional_repositories],
|
'optional_repositories': [r.value for r in self.optional_repositories],
|
||||||
"custom_repositories": [c.json() for c in self.custom_repositories],
|
'custom_repositories': [c.json() for c in self.custom_repositories],
|
||||||
}
|
}
|
||||||
|
|
||||||
def custom_servers_config(self) -> str:
|
def custom_servers_config(self) -> str:
|
||||||
config = ""
|
config = ''
|
||||||
|
|
||||||
if self.custom_servers:
|
if self.custom_servers:
|
||||||
config += "## Custom Servers\n"
|
config += '## Custom Servers\n'
|
||||||
for server in self.custom_servers:
|
for server in self.custom_servers:
|
||||||
config += f"Server = {server.url}\n"
|
config += f'Server = {server.url}\n'
|
||||||
|
|
||||||
return config.strip()
|
return config.strip()
|
||||||
|
|
||||||
def regions_config(self, speed_sort: bool = True) -> str:
|
def regions_config(self, speed_sort: bool = True) -> str:
|
||||||
from ..mirrors import mirror_list_handler
|
from ..mirrors import mirror_list_handler
|
||||||
|
|
||||||
config = ""
|
config = ''
|
||||||
|
|
||||||
for mirror_region in self.mirror_regions:
|
for mirror_region in self.mirror_regions:
|
||||||
sorted_stati = mirror_list_handler.get_status_by_region(
|
sorted_stati = mirror_list_handler.get_status_by_region(
|
||||||
|
|
@ -278,20 +278,20 @@ class MirrorConfiguration:
|
||||||
speed_sort=speed_sort,
|
speed_sort=speed_sort,
|
||||||
)
|
)
|
||||||
|
|
||||||
config += f"\n\n## {mirror_region.name}\n"
|
config += f'\n\n## {mirror_region.name}\n'
|
||||||
|
|
||||||
for status in sorted_stati:
|
for status in sorted_stati:
|
||||||
config += f"Server = {status.server_url}\n"
|
config += f'Server = {status.server_url}\n'
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def repositories_config(self) -> str:
|
def repositories_config(self) -> str:
|
||||||
config = ""
|
config = ''
|
||||||
|
|
||||||
for repo in self.custom_repositories:
|
for repo in self.custom_repositories:
|
||||||
config += f"\n\n[{repo.name}]\n"
|
config += f'\n\n[{repo.name}]\n'
|
||||||
config += f"SigLevel = {repo.sign_check.value} {repo.sign_option.value}\n"
|
config += f'SigLevel = {repo.sign_check.value} {repo.sign_option.value}\n'
|
||||||
config += f"Server = {repo.url}\n"
|
config += f'Server = {repo.url}\n'
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
@ -300,25 +300,25 @@ class MirrorConfiguration:
|
||||||
cls,
|
cls,
|
||||||
args: dict[str, Any],
|
args: dict[str, Any],
|
||||||
backwards_compatible_repo: list[Repository] = [],
|
backwards_compatible_repo: list[Repository] = [],
|
||||||
) -> "MirrorConfiguration":
|
) -> 'MirrorConfiguration':
|
||||||
config = MirrorConfiguration()
|
config = MirrorConfiguration()
|
||||||
|
|
||||||
mirror_regions = args.get("mirror_regions", [])
|
mirror_regions = args.get('mirror_regions', [])
|
||||||
if mirror_regions:
|
if mirror_regions:
|
||||||
for region, urls in mirror_regions.items():
|
for region, urls in mirror_regions.items():
|
||||||
config.mirror_regions.append(MirrorRegion(region, urls))
|
config.mirror_regions.append(MirrorRegion(region, urls))
|
||||||
|
|
||||||
if args.get("custom_servers"):
|
if args.get('custom_servers'):
|
||||||
config.custom_servers = CustomServer.parse_args(args["custom_servers"])
|
config.custom_servers = CustomServer.parse_args(args['custom_servers'])
|
||||||
|
|
||||||
# backwards compatibility with the new custom_repository
|
# backwards compatibility with the new custom_repository
|
||||||
if "custom_mirrors" in args:
|
if 'custom_mirrors' in args:
|
||||||
config.custom_repositories = CustomRepository.parse_args(args["custom_mirrors"])
|
config.custom_repositories = CustomRepository.parse_args(args['custom_mirrors'])
|
||||||
if "custom_repositories" in args:
|
if 'custom_repositories' in args:
|
||||||
config.custom_repositories = CustomRepository.parse_args(args["custom_repositories"])
|
config.custom_repositories = CustomRepository.parse_args(args['custom_repositories'])
|
||||||
|
|
||||||
if "optional_repositories" in args:
|
if 'optional_repositories' in args:
|
||||||
config.optional_repositories = [Repository(r) for r in args["optional_repositories"]]
|
config.optional_repositories = [Repository(r) for r in args['optional_repositories']]
|
||||||
|
|
||||||
if backwards_compatible_repo:
|
if backwards_compatible_repo:
|
||||||
for r in backwards_compatible_repo:
|
for r in backwards_compatible_repo:
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,18 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class NicType(Enum):
|
class NicType(Enum):
|
||||||
ISO = "iso"
|
ISO = 'iso'
|
||||||
NM = "nm"
|
NM = 'nm'
|
||||||
MANUAL = "manual"
|
MANUAL = 'manual'
|
||||||
|
|
||||||
def display_msg(self) -> str:
|
def display_msg(self) -> str:
|
||||||
match self:
|
match self:
|
||||||
case NicType.ISO:
|
case NicType.ISO:
|
||||||
return tr("Copy ISO network configuration to installation")
|
return tr('Copy ISO network configuration to installation')
|
||||||
case NicType.NM:
|
case NicType.NM:
|
||||||
return tr("Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)")
|
return tr('Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)')
|
||||||
case NicType.MANUAL:
|
case NicType.MANUAL:
|
||||||
return tr("Manual configuration")
|
return tr('Manual configuration')
|
||||||
|
|
||||||
|
|
||||||
class _NicSerialization(TypedDict):
|
class _NicSerialization(TypedDict):
|
||||||
|
|
@ -45,30 +45,30 @@ class Nic:
|
||||||
|
|
||||||
def table_data(self) -> dict[str, str | bool | list[str]]:
|
def table_data(self) -> dict[str, str | bool | list[str]]:
|
||||||
return {
|
return {
|
||||||
"iface": self.iface if self.iface else "",
|
'iface': self.iface if self.iface else '',
|
||||||
"ip": self.ip if self.ip else "",
|
'ip': self.ip if self.ip else '',
|
||||||
"dhcp": self.dhcp,
|
'dhcp': self.dhcp,
|
||||||
"gateway": self.gateway if self.gateway else "",
|
'gateway': self.gateway if self.gateway else '',
|
||||||
"dns": self.dns,
|
'dns': self.dns,
|
||||||
}
|
}
|
||||||
|
|
||||||
def json(self) -> _NicSerialization:
|
def json(self) -> _NicSerialization:
|
||||||
return {
|
return {
|
||||||
"iface": self.iface,
|
'iface': self.iface,
|
||||||
"ip": self.ip,
|
'ip': self.ip,
|
||||||
"dhcp": self.dhcp,
|
'dhcp': self.dhcp,
|
||||||
"gateway": self.gateway,
|
'gateway': self.gateway,
|
||||||
"dns": self.dns,
|
'dns': self.dns,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_arg(arg: _NicSerialization) -> Nic:
|
def parse_arg(arg: _NicSerialization) -> Nic:
|
||||||
return Nic(
|
return Nic(
|
||||||
iface=arg.get("iface", None),
|
iface=arg.get('iface', None),
|
||||||
ip=arg.get("ip", None),
|
ip=arg.get('ip', None),
|
||||||
dhcp=arg.get("dhcp", True),
|
dhcp=arg.get('dhcp', True),
|
||||||
gateway=arg.get("gateway", None),
|
gateway=arg.get('gateway', None),
|
||||||
dns=arg.get("dns", []),
|
dns=arg.get('dns', []),
|
||||||
)
|
)
|
||||||
|
|
||||||
def as_systemd_config(self) -> str:
|
def as_systemd_config(self) -> str:
|
||||||
|
|
@ -76,25 +76,25 @@ class Nic:
|
||||||
network: list[tuple[str, str]] = []
|
network: list[tuple[str, str]] = []
|
||||||
|
|
||||||
if self.iface:
|
if self.iface:
|
||||||
match.append(("Name", self.iface))
|
match.append(('Name', self.iface))
|
||||||
|
|
||||||
if self.dhcp:
|
if self.dhcp:
|
||||||
network.append(("DHCP", "yes"))
|
network.append(('DHCP', 'yes'))
|
||||||
else:
|
else:
|
||||||
if self.ip:
|
if self.ip:
|
||||||
network.append(("Address", self.ip))
|
network.append(('Address', self.ip))
|
||||||
if self.gateway:
|
if self.gateway:
|
||||||
network.append(("Gateway", self.gateway))
|
network.append(('Gateway', self.gateway))
|
||||||
for dns in self.dns:
|
for dns in self.dns:
|
||||||
network.append(("DNS", dns))
|
network.append(('DNS', dns))
|
||||||
|
|
||||||
config = {"Match": match, "Network": network}
|
config = {'Match': match, 'Network': network}
|
||||||
|
|
||||||
config_str = ""
|
config_str = ''
|
||||||
for top, entries in config.items():
|
for top, entries in config.items():
|
||||||
config_str += f"[{top}]\n"
|
config_str += f'[{top}]\n'
|
||||||
config_str += "\n".join([f"{k}={v}" for k, v in entries])
|
config_str += '\n'.join([f'{k}={v}' for k, v in entries])
|
||||||
config_str += "\n\n"
|
config_str += '\n\n'
|
||||||
|
|
||||||
return config_str
|
return config_str
|
||||||
|
|
||||||
|
|
@ -110,15 +110,15 @@ class NetworkConfiguration:
|
||||||
nics: list[Nic] = field(default_factory=list)
|
nics: list[Nic] = field(default_factory=list)
|
||||||
|
|
||||||
def json(self) -> _NetworkConfigurationSerialization:
|
def json(self) -> _NetworkConfigurationSerialization:
|
||||||
config: _NetworkConfigurationSerialization = {"type": self.type.value}
|
config: _NetworkConfigurationSerialization = {'type': self.type.value}
|
||||||
if self.nics:
|
if self.nics:
|
||||||
config["nics"] = [n.json() for n in self.nics]
|
config['nics'] = [n.json() for n in self.nics]
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_arg(config: _NetworkConfigurationSerialization) -> NetworkConfiguration | None:
|
def parse_arg(config: _NetworkConfigurationSerialization) -> NetworkConfiguration | None:
|
||||||
nic_type = config.get("type", None)
|
nic_type = config.get('type', None)
|
||||||
if not nic_type:
|
if not nic_type:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -128,7 +128,7 @@ class NetworkConfiguration:
|
||||||
case NicType.NM:
|
case NicType.NM:
|
||||||
return NetworkConfiguration(NicType.NM)
|
return NetworkConfiguration(NicType.NM)
|
||||||
case NicType.MANUAL:
|
case NicType.MANUAL:
|
||||||
nics_arg = config.get("nics", [])
|
nics_arg = config.get('nics', [])
|
||||||
if nics_arg:
|
if nics_arg:
|
||||||
nics = [Nic.parse_arg(n) for n in nics_arg]
|
nics = [Nic.parse_arg(n) for n in nics_arg]
|
||||||
return NetworkConfiguration(NicType.MANUAL, nics)
|
return NetworkConfiguration(NicType.MANUAL, nics)
|
||||||
|
|
@ -146,14 +146,14 @@ class NetworkConfiguration:
|
||||||
enable_services=True, # Sources the ISO network configuration to the install medium.
|
enable_services=True, # Sources the ISO network configuration to the install medium.
|
||||||
)
|
)
|
||||||
case NicType.NM:
|
case NicType.NM:
|
||||||
installation.add_additional_packages(["networkmanager"])
|
installation.add_additional_packages(['networkmanager'])
|
||||||
if profile_config and profile_config.profile:
|
if profile_config and profile_config.profile:
|
||||||
if profile_config.profile.is_desktop_profile():
|
if profile_config.profile.is_desktop_profile():
|
||||||
installation.add_additional_packages(["network-manager-applet"])
|
installation.add_additional_packages(['network-manager-applet'])
|
||||||
installation.enable_service("NetworkManager.service")
|
installation.enable_service('NetworkManager.service')
|
||||||
case NicType.MANUAL:
|
case NicType.MANUAL:
|
||||||
for nic in self.nics:
|
for nic in self.nics:
|
||||||
installation.configure_nic(nic)
|
installation.configure_nic(nic)
|
||||||
|
|
||||||
installation.enable_service("systemd-networkd")
|
installation.enable_service('systemd-networkd')
|
||||||
installation.enable_service("systemd-resolved")
|
installation.enable_service('systemd-resolved')
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ from archinstall.lib.translationhandler import tr
|
||||||
|
|
||||||
|
|
||||||
class Repository(Enum):
|
class Repository(Enum):
|
||||||
Core = "core"
|
Core = 'core'
|
||||||
Extra = "extra"
|
Extra = 'extra'
|
||||||
Multilib = "multilib"
|
Multilib = 'multilib'
|
||||||
Testing = "testing"
|
Testing = 'testing'
|
||||||
|
|
||||||
def get_repository_list(self) -> list[str]:
|
def get_repository_list(self) -> list[str]:
|
||||||
match self:
|
match self:
|
||||||
|
|
@ -24,9 +24,9 @@ class Repository(Enum):
|
||||||
return [Repository.Multilib.value]
|
return [Repository.Multilib.value]
|
||||||
case Repository.Testing:
|
case Repository.Testing:
|
||||||
return [
|
return [
|
||||||
"core-testing",
|
'core-testing',
|
||||||
"extra-testing",
|
'extra-testing',
|
||||||
"multilib-testing",
|
'multilib-testing',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ class PackageSearchResult:
|
||||||
checkdepends: list[str]
|
checkdepends: list[str]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_json(data: dict[str, Any]) -> "PackageSearchResult":
|
def from_json(data: dict[str, Any]) -> 'PackageSearchResult':
|
||||||
return PackageSearchResult(**data)
|
return PackageSearchResult(**data)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -74,7 +74,7 @@ class PackageSearchResult:
|
||||||
|
|
||||||
return self.pkg_version == other.pkg_version
|
return self.pkg_version == other.pkg_version
|
||||||
|
|
||||||
def __lt__(self, other: "PackageSearchResult") -> bool:
|
def __lt__(self, other: 'PackageSearchResult') -> bool:
|
||||||
return self.pkg_version < other.pkg_version
|
return self.pkg_version < other.pkg_version
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -88,15 +88,15 @@ class PackageSearch:
|
||||||
results: list[PackageSearchResult]
|
results: list[PackageSearchResult]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_json(data: dict[str, Any]) -> "PackageSearch":
|
def from_json(data: dict[str, Any]) -> 'PackageSearch':
|
||||||
results = [PackageSearchResult.from_json(r) for r in data["results"]]
|
results = [PackageSearchResult.from_json(r) for r in data['results']]
|
||||||
|
|
||||||
return PackageSearch(
|
return PackageSearch(
|
||||||
version=data["version"],
|
version=data['version'],
|
||||||
limit=data["limit"],
|
limit=data['limit'],
|
||||||
valid=data["valid"],
|
valid=data['valid'],
|
||||||
num_pages=data["num_pages"],
|
num_pages=data['num_pages'],
|
||||||
page=data["page"],
|
page=data['page'],
|
||||||
results=results,
|
results=results,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -132,7 +132,7 @@ class LocalPackage(BaseModel):
|
||||||
|
|
||||||
return self.version == other.version
|
return self.version == other.version
|
||||||
|
|
||||||
def __lt__(self, other: "LocalPackage") -> bool:
|
def __lt__(self, other: 'LocalPackage') -> bool:
|
||||||
return self.version < other.version
|
return self.version < other.version
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -161,11 +161,11 @@ class AvailablePackage(BaseModel):
|
||||||
|
|
||||||
# return all package info line by line
|
# return all package info line by line
|
||||||
def info(self) -> str:
|
def info(self) -> str:
|
||||||
output = ""
|
output = ''
|
||||||
for key, value in self.model_dump().items():
|
for key, value in self.model_dump().items():
|
||||||
key = key.replace("_", " ").capitalize()
|
key = key.replace('_', ' ').capitalize()
|
||||||
key = key.ljust(self.longest_key)
|
key = key.ljust(self.longest_key)
|
||||||
output += f"{key} : {value}\n"
|
output += f'{key} : {value}\n'
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
@ -179,14 +179,14 @@ class PackageGroup:
|
||||||
def from_available_packages(
|
def from_available_packages(
|
||||||
cls,
|
cls,
|
||||||
packages: dict[str, AvailablePackage],
|
packages: dict[str, AvailablePackage],
|
||||||
) -> dict[str, "PackageGroup"]:
|
) -> dict[str, 'PackageGroup']:
|
||||||
pkg_groups: dict[str, "PackageGroup"] = {}
|
pkg_groups: dict[str, 'PackageGroup'] = {}
|
||||||
|
|
||||||
for pkg in packages.values():
|
for pkg in packages.values():
|
||||||
if "None" in pkg.groups:
|
if 'None' in pkg.groups:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
groups = pkg.groups.split(" ")
|
groups = pkg.groups.split(' ')
|
||||||
|
|
||||||
for group in groups:
|
for group in groups:
|
||||||
# same group names have multiple spaces in between
|
# same group names have multiple spaces in between
|
||||||
|
|
@ -199,6 +199,6 @@ class PackageGroup:
|
||||||
return pkg_groups
|
return pkg_groups
|
||||||
|
|
||||||
def info(self) -> str:
|
def info(self) -> str:
|
||||||
output = tr("Package group:") + "\n - "
|
output = tr('Package group:') + '\n - '
|
||||||
output += "\n - ".join(self.packages)
|
output += '\n - '.join(self.packages)
|
||||||
return output
|
return output
|
||||||
|
|
|
||||||
|
|
@ -27,18 +27,18 @@ class ProfileConfiguration:
|
||||||
from ..profile.profiles_handler import profile_handler
|
from ..profile.profiles_handler import profile_handler
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"profile": profile_handler.to_json(self.profile),
|
'profile': profile_handler.to_json(self.profile),
|
||||||
"gfx_driver": self.gfx_driver.value if self.gfx_driver else None,
|
'gfx_driver': self.gfx_driver.value if self.gfx_driver else None,
|
||||||
"greeter": self.greeter.value if self.greeter else None,
|
'greeter': self.greeter.value if self.greeter else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_arg(cls, arg: _ProfileConfigurationSerialization) -> "ProfileConfiguration":
|
def parse_arg(cls, arg: _ProfileConfigurationSerialization) -> 'ProfileConfiguration':
|
||||||
from ..profile.profiles_handler import profile_handler
|
from ..profile.profiles_handler import profile_handler
|
||||||
|
|
||||||
profile = profile_handler.parse_profile_config(arg["profile"])
|
profile = profile_handler.parse_profile_config(arg['profile'])
|
||||||
greeter = arg.get("greeter", None)
|
greeter = arg.get('greeter', None)
|
||||||
gfx_driver = arg.get("gfx_driver", None)
|
gfx_driver = arg.get('gfx_driver', None)
|
||||||
|
|
||||||
return ProfileConfiguration(
|
return ProfileConfiguration(
|
||||||
profile,
|
profile,
|
||||||
|
|
|
||||||
|
|
@ -8,37 +8,37 @@ from ..crypt import crypt_yescrypt
|
||||||
|
|
||||||
|
|
||||||
class PasswordStrength(Enum):
|
class PasswordStrength(Enum):
|
||||||
VERY_WEAK = "very weak"
|
VERY_WEAK = 'very weak'
|
||||||
WEAK = "weak"
|
WEAK = 'weak'
|
||||||
MODERATE = "moderate"
|
MODERATE = 'moderate'
|
||||||
STRONG = "strong"
|
STRONG = 'strong'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def value(self) -> str: # pylint: disable=invalid-overridden-method
|
def value(self) -> str: # pylint: disable=invalid-overridden-method
|
||||||
match self:
|
match self:
|
||||||
case PasswordStrength.VERY_WEAK:
|
case PasswordStrength.VERY_WEAK:
|
||||||
return tr("very weak")
|
return tr('very weak')
|
||||||
case PasswordStrength.WEAK:
|
case PasswordStrength.WEAK:
|
||||||
return tr("weak")
|
return tr('weak')
|
||||||
case PasswordStrength.MODERATE:
|
case PasswordStrength.MODERATE:
|
||||||
return tr("moderate")
|
return tr('moderate')
|
||||||
case PasswordStrength.STRONG:
|
case PasswordStrength.STRONG:
|
||||||
return tr("strong")
|
return tr('strong')
|
||||||
|
|
||||||
def color(self) -> str:
|
def color(self) -> str:
|
||||||
match self:
|
match self:
|
||||||
case PasswordStrength.VERY_WEAK:
|
case PasswordStrength.VERY_WEAK:
|
||||||
return "red"
|
return 'red'
|
||||||
case PasswordStrength.WEAK:
|
case PasswordStrength.WEAK:
|
||||||
return "red"
|
return 'red'
|
||||||
case PasswordStrength.MODERATE:
|
case PasswordStrength.MODERATE:
|
||||||
return "yellow"
|
return 'yellow'
|
||||||
case PasswordStrength.STRONG:
|
case PasswordStrength.STRONG:
|
||||||
return "green"
|
return 'green'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def strength(cls, password: str) -> "PasswordStrength":
|
def strength(cls, password: str) -> 'PasswordStrength':
|
||||||
digit = any(character.isdigit() for character in password)
|
digit = any(character.isdigit() for character in password)
|
||||||
upper = any(character.isupper() for character in password)
|
upper = any(character.isupper() for character in password)
|
||||||
lower = any(character.islower() for character in password)
|
lower = any(character.islower() for character in password)
|
||||||
|
|
@ -53,7 +53,7 @@ class PasswordStrength(Enum):
|
||||||
lower: bool,
|
lower: bool,
|
||||||
symbol: bool,
|
symbol: bool,
|
||||||
length: int,
|
length: int,
|
||||||
) -> "PasswordStrength":
|
) -> 'PasswordStrength':
|
||||||
# suggested evaluation
|
# suggested evaluation
|
||||||
# https://github.com/archlinux/archinstall/issues/1304#issuecomment-1146768163
|
# https://github.com/archlinux/archinstall/issues/1304#issuecomment-1146768163
|
||||||
if digit and upper and lower and symbol:
|
if digit and upper and lower and symbol:
|
||||||
|
|
@ -101,13 +101,13 @@ class PasswordStrength(Enum):
|
||||||
|
|
||||||
|
|
||||||
_UserSerialization = TypedDict(
|
_UserSerialization = TypedDict(
|
||||||
"_UserSerialization",
|
'_UserSerialization',
|
||||||
{
|
{
|
||||||
"username": str,
|
'username': str,
|
||||||
"!password": NotRequired[str],
|
'!password': NotRequired[str],
|
||||||
"sudo": bool,
|
'sudo': bool,
|
||||||
"groups": list[str],
|
'groups': list[str],
|
||||||
"enc_password": str | None,
|
'enc_password': str | None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -115,14 +115,14 @@ _UserSerialization = TypedDict(
|
||||||
class Password:
|
class Password:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
plaintext: str = "",
|
plaintext: str = '',
|
||||||
enc_password: str | None = None,
|
enc_password: str | None = None,
|
||||||
):
|
):
|
||||||
if plaintext:
|
if plaintext:
|
||||||
enc_password = crypt_yescrypt(plaintext)
|
enc_password = crypt_yescrypt(plaintext)
|
||||||
|
|
||||||
if not plaintext and not enc_password:
|
if not plaintext and not enc_password:
|
||||||
raise ValueError("Either plaintext or enc_password must be provided")
|
raise ValueError('Either plaintext or enc_password must be provided')
|
||||||
|
|
||||||
self._plaintext = plaintext
|
self._plaintext = plaintext
|
||||||
self.enc_password = enc_password
|
self.enc_password = enc_password
|
||||||
|
|
@ -148,9 +148,9 @@ class Password:
|
||||||
|
|
||||||
def hidden(self) -> str:
|
def hidden(self) -> str:
|
||||||
if self._plaintext:
|
if self._plaintext:
|
||||||
return "*" * len(self._plaintext)
|
return '*' * len(self._plaintext)
|
||||||
else:
|
else:
|
||||||
return "*" * 8
|
return '*' * 8
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -163,37 +163,37 @@ class User:
|
||||||
@override
|
@override
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
# safety overwrite to make sure password is not leaked
|
# safety overwrite to make sure password is not leaked
|
||||||
return f"User({self.username=}, {self.sudo=}, {self.groups=})"
|
return f'User({self.username=}, {self.sudo=}, {self.groups=})'
|
||||||
|
|
||||||
def table_data(self) -> dict[str, str | bool | list[str]]:
|
def table_data(self) -> dict[str, str | bool | list[str]]:
|
||||||
return {
|
return {
|
||||||
"username": self.username,
|
'username': self.username,
|
||||||
"password": self.password.hidden(),
|
'password': self.password.hidden(),
|
||||||
"sudo": self.sudo,
|
'sudo': self.sudo,
|
||||||
"groups": self.groups,
|
'groups': self.groups,
|
||||||
}
|
}
|
||||||
|
|
||||||
def json(self) -> _UserSerialization:
|
def json(self) -> _UserSerialization:
|
||||||
return {
|
return {
|
||||||
"username": self.username,
|
'username': self.username,
|
||||||
"enc_password": self.password.enc_password,
|
'enc_password': self.password.enc_password,
|
||||||
"sudo": self.sudo,
|
'sudo': self.sudo,
|
||||||
"groups": self.groups,
|
'groups': self.groups,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_arguments(
|
def parse_arguments(
|
||||||
cls,
|
cls,
|
||||||
args: list[_UserSerialization],
|
args: list[_UserSerialization],
|
||||||
) -> list["User"]:
|
) -> list['User']:
|
||||||
users: list[User] = []
|
users: list[User] = []
|
||||||
|
|
||||||
for entry in args:
|
for entry in args:
|
||||||
username = entry.get("username")
|
username = entry.get('username')
|
||||||
password: Password | None = None
|
password: Password | None = None
|
||||||
groups = entry.get("groups", [])
|
groups = entry.get('groups', [])
|
||||||
plaintext = entry.get("!password")
|
plaintext = entry.get('!password')
|
||||||
enc_password = entry.get("enc_password")
|
enc_password = entry.get('enc_password')
|
||||||
|
|
||||||
# DEPRECATED: backwards compatibility
|
# DEPRECATED: backwards compatibility
|
||||||
if plaintext:
|
if plaintext:
|
||||||
|
|
@ -207,7 +207,7 @@ class User:
|
||||||
user = User(
|
user = User(
|
||||||
username=username,
|
username=username,
|
||||||
password=password,
|
password=password,
|
||||||
sudo=entry.get("sudo", False) is True,
|
sudo=entry.get('sudo', False) is True,
|
||||||
groups=groups,
|
groups=groups,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class DownloadTimer:
|
||||||
"""
|
"""
|
||||||
Raise the DownloadTimeout exception.
|
Raise the DownloadTimeout exception.
|
||||||
"""
|
"""
|
||||||
raise DownloadTimeout(f"Download timed out after {self.timeout} second(s).")
|
raise DownloadTimeout(f'Download timed out after {self.timeout} second(s).')
|
||||||
|
|
||||||
def __enter__(self) -> Self:
|
def __enter__(self) -> Self:
|
||||||
if self.timeout > 0:
|
if self.timeout > 0:
|
||||||
|
|
@ -72,27 +72,27 @@ def get_hw_addr(ifname: str) -> str:
|
||||||
import fcntl
|
import fcntl
|
||||||
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
ret = fcntl.ioctl(s.fileno(), 0x8927, struct.pack("256s", bytes(ifname, "utf-8")[:15]))
|
ret = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', bytes(ifname, 'utf-8')[:15]))
|
||||||
return ":".join(f"{b:02x}" for b in ret[18:24])
|
return ':'.join(f'{b:02x}' for b in ret[18:24])
|
||||||
|
|
||||||
|
|
||||||
def list_interfaces(skip_loopback: bool = True) -> dict[str, str]:
|
def list_interfaces(skip_loopback: bool = True) -> dict[str, str]:
|
||||||
interfaces = {}
|
interfaces = {}
|
||||||
|
|
||||||
for _index, iface in socket.if_nameindex():
|
for _index, iface in socket.if_nameindex():
|
||||||
if skip_loopback and iface == "lo":
|
if skip_loopback and iface == 'lo':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
mac = get_hw_addr(iface).replace(":", "-").lower()
|
mac = get_hw_addr(iface).replace(':', '-').lower()
|
||||||
interfaces[mac] = iface
|
interfaces[mac] = iface
|
||||||
|
|
||||||
return interfaces
|
return interfaces
|
||||||
|
|
||||||
|
|
||||||
def update_keyring() -> bool:
|
def update_keyring() -> bool:
|
||||||
info("Updating archlinux-keyring ...")
|
info('Updating archlinux-keyring ...')
|
||||||
try:
|
try:
|
||||||
Pacman.run("-Sy --noconfirm archlinux-keyring")
|
Pacman.run('-Sy --noconfirm archlinux-keyring')
|
||||||
return True
|
return True
|
||||||
except SysCallError:
|
except SysCallError:
|
||||||
if os.geteuid() != 0:
|
if os.geteuid() != 0:
|
||||||
|
|
@ -105,18 +105,18 @@ def enrich_iface_types(interfaces: list[str]) -> dict[str, str]:
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
for iface in interfaces:
|
for iface in interfaces:
|
||||||
if os.path.isdir(f"/sys/class/net/{iface}/bridge/"):
|
if os.path.isdir(f'/sys/class/net/{iface}/bridge/'):
|
||||||
result[iface] = "BRIDGE"
|
result[iface] = 'BRIDGE'
|
||||||
elif os.path.isfile(f"/sys/class/net/{iface}/tun_flags"):
|
elif os.path.isfile(f'/sys/class/net/{iface}/tun_flags'):
|
||||||
# ethtool -i {iface}
|
# ethtool -i {iface}
|
||||||
result[iface] = "TUN/TAP"
|
result[iface] = 'TUN/TAP'
|
||||||
elif os.path.isdir(f"/sys/class/net/{iface}/device"):
|
elif os.path.isdir(f'/sys/class/net/{iface}/device'):
|
||||||
if os.path.isdir(f"/sys/class/net/{iface}/wireless/"):
|
if os.path.isdir(f'/sys/class/net/{iface}/wireless/'):
|
||||||
result[iface] = "WIRELESS"
|
result[iface] = 'WIRELESS'
|
||||||
else:
|
else:
|
||||||
result[iface] = "PHYSICAL"
|
result[iface] = 'PHYSICAL'
|
||||||
else:
|
else:
|
||||||
result[iface] = "UNKNOWN"
|
result[iface] = 'UNKNOWN'
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
@ -128,25 +128,25 @@ def fetch_data_from_url(url: str, params: dict[str, str] | None = None) -> str:
|
||||||
|
|
||||||
if params is not None:
|
if params is not None:
|
||||||
encoded = urlencode(params)
|
encoded = urlencode(params)
|
||||||
full_url = f"{url}?{encoded}"
|
full_url = f'{url}?{encoded}'
|
||||||
else:
|
else:
|
||||||
full_url = url
|
full_url = url
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = urlopen(full_url, context=ssl_context)
|
response = urlopen(full_url, context=ssl_context)
|
||||||
data = response.read().decode("UTF-8")
|
data = response.read().decode('UTF-8')
|
||||||
return data
|
return data
|
||||||
except URLError as e:
|
except URLError as e:
|
||||||
raise ValueError(f"Unable to fetch data from url: {url}\n{e}")
|
raise ValueError(f'Unable to fetch data from url: {url}\n{e}')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Unexpected error when parsing response: {e}")
|
raise ValueError(f'Unexpected error when parsing response: {e}')
|
||||||
|
|
||||||
|
|
||||||
def calc_checksum(icmp_packet: bytes) -> int:
|
def calc_checksum(icmp_packet: bytes) -> int:
|
||||||
# Calculate the ICMP checksum
|
# Calculate the ICMP checksum
|
||||||
checksum = 0
|
checksum = 0
|
||||||
for i in range(0, len(icmp_packet), 2):
|
for i in range(0, len(icmp_packet), 2):
|
||||||
checksum += (icmp_packet[i] << 8) + (struct.unpack("B", icmp_packet[i + 1 : i + 2])[0] if len(icmp_packet[i + 1 : i + 2]) else 0)
|
checksum += (icmp_packet[i] << 8) + (struct.unpack('B', icmp_packet[i + 1 : i + 2])[0] if len(icmp_packet[i + 1 : i + 2]) else 0)
|
||||||
|
|
||||||
checksum = (checksum >> 16) + (checksum & 0xFFFF)
|
checksum = (checksum >> 16) + (checksum & 0xFFFF)
|
||||||
checksum = ~checksum & 0xFFFF
|
checksum = ~checksum & 0xFFFF
|
||||||
|
|
@ -156,17 +156,17 @@ def calc_checksum(icmp_packet: bytes) -> int:
|
||||||
|
|
||||||
def build_icmp(payload: bytes) -> bytes:
|
def build_icmp(payload: bytes) -> bytes:
|
||||||
# Define the ICMP Echo Request packet
|
# Define the ICMP Echo Request packet
|
||||||
icmp_packet = struct.pack("!BBHHH", 8, 0, 0, 0, 1) + payload
|
icmp_packet = struct.pack('!BBHHH', 8, 0, 0, 0, 1) + payload
|
||||||
|
|
||||||
checksum = calc_checksum(icmp_packet)
|
checksum = calc_checksum(icmp_packet)
|
||||||
|
|
||||||
return struct.pack("!BBHHH", 8, 0, checksum, 0, 1) + payload
|
return struct.pack('!BBHHH', 8, 0, checksum, 0, 1) + payload
|
||||||
|
|
||||||
|
|
||||||
def ping(hostname, timeout: int = 5) -> int:
|
def ping(hostname, timeout: int = 5) -> int:
|
||||||
watchdog = select.epoll()
|
watchdog = select.epoll()
|
||||||
started = time.time()
|
started = time.time()
|
||||||
random_identifier = f"archinstall-{random.randint(1000, 9999)}".encode()
|
random_identifier = f'archinstall-{random.randint(1000, 9999)}'.encode()
|
||||||
|
|
||||||
# Create a raw socket (requires root, which should be fine on archiso)
|
# Create a raw socket (requires root, which should be fine on archiso)
|
||||||
icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
|
icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
|
||||||
|
|
@ -184,14 +184,14 @@ def ping(hostname, timeout: int = 5) -> int:
|
||||||
try:
|
try:
|
||||||
for _fileno, _event in watchdog.poll(0.1):
|
for _fileno, _event in watchdog.poll(0.1):
|
||||||
response, _ = icmp_socket.recvfrom(1024)
|
response, _ = icmp_socket.recvfrom(1024)
|
||||||
icmp_type = struct.unpack("!B", response[20:21])[0]
|
icmp_type = struct.unpack('!B', response[20:21])[0]
|
||||||
|
|
||||||
# Check if it's an Echo Reply (ICMP type 0)
|
# Check if it's an Echo Reply (ICMP type 0)
|
||||||
if icmp_type == 0 and response[-len(random_identifier) :] == random_identifier:
|
if icmp_type == 0 and response[-len(random_identifier) :] == random_identifier:
|
||||||
latency = round((time.time() - started) * 1000)
|
latency = round((time.time() - started) * 1000)
|
||||||
break
|
break
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
debug(f"Error: {e}")
|
debug(f'Error: {e}')
|
||||||
break
|
break
|
||||||
|
|
||||||
icmp_socket.close()
|
icmp_socket.close()
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class FormattedOutput:
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_values(
|
def _get_values(
|
||||||
cls,
|
cls,
|
||||||
o: "DataclassInstance",
|
o: 'DataclassInstance',
|
||||||
class_formatter: str | Callable | None = None, # type: ignore[type-arg]
|
class_formatter: str | Callable | None = None, # type: ignore[type-arg]
|
||||||
filter_list: list[str] = [],
|
filter_list: list[str] = [],
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
|
@ -38,10 +38,10 @@ class FormattedOutput:
|
||||||
func = getattr(o, class_formatter)
|
func = getattr(o, class_formatter)
|
||||||
return func(filter_list)
|
return func(filter_list)
|
||||||
|
|
||||||
raise ValueError("Unsupported formatting call")
|
raise ValueError('Unsupported formatting call')
|
||||||
elif hasattr(o, "table_data"):
|
elif hasattr(o, 'table_data'):
|
||||||
return o.table_data()
|
return o.table_data()
|
||||||
elif hasattr(o, "json"):
|
elif hasattr(o, 'json'):
|
||||||
return o.json()
|
return o.json()
|
||||||
elif is_dataclass(o):
|
elif is_dataclass(o):
|
||||||
return asdict(o)
|
return asdict(o)
|
||||||
|
|
@ -78,36 +78,36 @@ class FormattedOutput:
|
||||||
filter_list = list(column_width.keys())
|
filter_list = list(column_width.keys())
|
||||||
|
|
||||||
# create the header lines
|
# create the header lines
|
||||||
output = ""
|
output = ''
|
||||||
key_list = []
|
key_list = []
|
||||||
for key in filter_list:
|
for key in filter_list:
|
||||||
width = column_width[key]
|
width = column_width[key]
|
||||||
key = key.replace("!", "").replace("_", " ")
|
key = key.replace('!', '').replace('_', ' ')
|
||||||
|
|
||||||
if capitalize:
|
if capitalize:
|
||||||
key = key.capitalize()
|
key = key.capitalize()
|
||||||
|
|
||||||
key_list.append(unicode_ljust(key, width))
|
key_list.append(unicode_ljust(key, width))
|
||||||
|
|
||||||
output += " | ".join(key_list) + "\n"
|
output += ' | '.join(key_list) + '\n'
|
||||||
output += "-" * len(output) + "\n"
|
output += '-' * len(output) + '\n'
|
||||||
|
|
||||||
# create the data lines
|
# create the data lines
|
||||||
for record in raw_data:
|
for record in raw_data:
|
||||||
obj_data = []
|
obj_data = []
|
||||||
for key in filter_list:
|
for key in filter_list:
|
||||||
width = column_width.get(key, len(key))
|
width = column_width.get(key, len(key))
|
||||||
value = record.get(key, "")
|
value = record.get(key, '')
|
||||||
|
|
||||||
if "!" in key:
|
if '!' in key:
|
||||||
value = "*" * len(value)
|
value = '*' * len(value)
|
||||||
|
|
||||||
if isinstance(value, int | float) or (isinstance(value, str) and value.isnumeric()):
|
if isinstance(value, int | float) or (isinstance(value, str) and value.isnumeric()):
|
||||||
obj_data.append(unicode_rjust(str(value), width))
|
obj_data.append(unicode_rjust(str(value), width))
|
||||||
else:
|
else:
|
||||||
obj_data.append(unicode_ljust(str(value), width))
|
obj_data.append(unicode_ljust(str(value), width))
|
||||||
|
|
||||||
output += " | ".join(obj_data) + "\n"
|
output += ' | '.join(obj_data) + '\n'
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
@ -117,14 +117,14 @@ class FormattedOutput:
|
||||||
Will format a list into a given number of columns
|
Will format a list into a given number of columns
|
||||||
"""
|
"""
|
||||||
chunks = []
|
chunks = []
|
||||||
output = ""
|
output = ''
|
||||||
|
|
||||||
for i in range(0, len(entries), cols):
|
for i in range(0, len(entries), cols):
|
||||||
chunks.append(entries[i : i + cols])
|
chunks.append(entries[i : i + cols])
|
||||||
|
|
||||||
for row in chunks:
|
for row in chunks:
|
||||||
out_fmt = "{: <30} " * len(row)
|
out_fmt = '{: <30} ' * len(row)
|
||||||
output += out_fmt.format(*row) + "\n"
|
output += out_fmt.format(*row) + '\n'
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
@ -137,8 +137,8 @@ class Journald:
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
log_adapter = logging.getLogger("archinstall")
|
log_adapter = logging.getLogger('archinstall')
|
||||||
log_fmt = logging.Formatter("[%(levelname)s]: %(message)s")
|
log_fmt = logging.Formatter('[%(levelname)s]: %(message)s')
|
||||||
log_ch = systemd.journal.JournalHandler()
|
log_ch = systemd.journal.JournalHandler()
|
||||||
log_ch.setFormatter(log_fmt)
|
log_ch.setFormatter(log_fmt)
|
||||||
log_adapter.addHandler(log_ch)
|
log_adapter.addHandler(log_ch)
|
||||||
|
|
@ -148,11 +148,11 @@ class Journald:
|
||||||
|
|
||||||
|
|
||||||
def _check_log_permissions() -> None:
|
def _check_log_permissions() -> None:
|
||||||
filename = storage.get("LOG_FILE", None)
|
filename = storage.get('LOG_FILE', None)
|
||||||
log_dir = storage.get("LOG_PATH", Path("./"))
|
log_dir = storage.get('LOG_PATH', Path('./'))
|
||||||
|
|
||||||
if not filename:
|
if not filename:
|
||||||
raise ValueError("No log file name defined")
|
raise ValueError('No log file name defined')
|
||||||
|
|
||||||
log_file = log_dir / filename
|
log_file = log_dir / filename
|
||||||
|
|
||||||
|
|
@ -160,17 +160,17 @@ def _check_log_permissions() -> None:
|
||||||
log_dir.mkdir(exist_ok=True, parents=True)
|
log_dir.mkdir(exist_ok=True, parents=True)
|
||||||
log_file.touch(exist_ok=True)
|
log_file.touch(exist_ok=True)
|
||||||
|
|
||||||
with log_file.open("a") as fp:
|
with log_file.open('a') as fp:
|
||||||
fp.write("")
|
fp.write('')
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
# Fallback to creating the log file in the current folder
|
# Fallback to creating the log file in the current folder
|
||||||
fallback_dir = Path("./").absolute()
|
fallback_dir = Path('./').absolute()
|
||||||
fallback_log_file = fallback_dir / filename
|
fallback_log_file = fallback_dir / filename
|
||||||
|
|
||||||
fallback_log_file.touch(exist_ok=True)
|
fallback_log_file.touch(exist_ok=True)
|
||||||
|
|
||||||
storage["LOG_PATH"] = fallback_dir
|
storage['LOG_PATH'] = fallback_dir
|
||||||
warn(f"Not enough permission to place log file at {log_file}, creating it in {fallback_log_file} instead")
|
warn(f'Not enough permission to place log file at {log_file}, creating it in {fallback_log_file} instead')
|
||||||
|
|
||||||
|
|
||||||
def _supports_color() -> bool:
|
def _supports_color() -> bool:
|
||||||
|
|
@ -183,20 +183,20 @@ def _supports_color() -> bool:
|
||||||
Return True if the running system's terminal supports color,
|
Return True if the running system's terminal supports color,
|
||||||
and False otherwise.
|
and False otherwise.
|
||||||
"""
|
"""
|
||||||
supported_platform = sys.platform != "win32" or "ANSICON" in os.environ
|
supported_platform = sys.platform != 'win32' or 'ANSICON' in os.environ
|
||||||
|
|
||||||
# isatty is not always implemented, #6223.
|
# isatty is not always implemented, #6223.
|
||||||
is_a_tty = hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
|
is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
|
||||||
return supported_platform and is_a_tty
|
return supported_platform and is_a_tty
|
||||||
|
|
||||||
|
|
||||||
class Font(Enum):
|
class Font(Enum):
|
||||||
bold = "1"
|
bold = '1'
|
||||||
italic = "3"
|
italic = '3'
|
||||||
underscore = "4"
|
underscore = '4'
|
||||||
blink = "5"
|
blink = '5'
|
||||||
reverse = "7"
|
reverse = '7'
|
||||||
conceal = "8"
|
conceal = '8'
|
||||||
|
|
||||||
|
|
||||||
def _stylize_output(
|
def _stylize_output(
|
||||||
|
|
@ -215,29 +215,29 @@ def _stylize_output(
|
||||||
Adds styling to a text given a set of color arguments.
|
Adds styling to a text given a set of color arguments.
|
||||||
"""
|
"""
|
||||||
colors = {
|
colors = {
|
||||||
"black": "0",
|
'black': '0',
|
||||||
"red": "1",
|
'red': '1',
|
||||||
"green": "2",
|
'green': '2',
|
||||||
"yellow": "3",
|
'yellow': '3',
|
||||||
"blue": "4",
|
'blue': '4',
|
||||||
"magenta": "5",
|
'magenta': '5',
|
||||||
"cyan": "6",
|
'cyan': '6',
|
||||||
"white": "7",
|
'white': '7',
|
||||||
"teal": "8;5;109", # Extended 256-bit colors (not always supported)
|
'teal': '8;5;109', # Extended 256-bit colors (not always supported)
|
||||||
"orange": "8;5;208", # https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
|
'orange': '8;5;208', # https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
|
||||||
"darkorange": "8;5;202",
|
'darkorange': '8;5;202',
|
||||||
"gray": "8;5;246",
|
'gray': '8;5;246',
|
||||||
"grey": "8;5;246",
|
'grey': '8;5;246',
|
||||||
"darkgray": "8;5;240",
|
'darkgray': '8;5;240',
|
||||||
"lightgray": "8;5;256",
|
'lightgray': '8;5;256',
|
||||||
}
|
}
|
||||||
|
|
||||||
foreground = {key: f"3{colors[key]}" for key in colors}
|
foreground = {key: f'3{colors[key]}' for key in colors}
|
||||||
background = {key: f"4{colors[key]}" for key in colors}
|
background = {key: f'4{colors[key]}' for key in colors}
|
||||||
code_list = []
|
code_list = []
|
||||||
|
|
||||||
if text == "" and reset:
|
if text == '' and reset:
|
||||||
return "\x1b[0m"
|
return '\x1b[0m'
|
||||||
|
|
||||||
code_list.append(foreground[str(fg)])
|
code_list.append(foreground[str(fg)])
|
||||||
|
|
||||||
|
|
@ -247,15 +247,15 @@ def _stylize_output(
|
||||||
for o in font:
|
for o in font:
|
||||||
code_list.append(o.value)
|
code_list.append(o.value)
|
||||||
|
|
||||||
ansi = ";".join(code_list)
|
ansi = ';'.join(code_list)
|
||||||
|
|
||||||
return f"\033[{ansi}m{text}\033[0m"
|
return f'\033[{ansi}m{text}\033[0m'
|
||||||
|
|
||||||
|
|
||||||
def info(
|
def info(
|
||||||
*msgs: str,
|
*msgs: str,
|
||||||
level: int = logging.INFO,
|
level: int = logging.INFO,
|
||||||
fg: str = "white",
|
fg: str = 'white',
|
||||||
bg: str | None = None,
|
bg: str | None = None,
|
||||||
reset: bool = False,
|
reset: bool = False,
|
||||||
font: list[Font] = [],
|
font: list[Font] = [],
|
||||||
|
|
@ -265,13 +265,13 @@ def info(
|
||||||
|
|
||||||
def _timestamp() -> str:
|
def _timestamp() -> str:
|
||||||
now = datetime.now(tz=UTC)
|
now = datetime.now(tz=UTC)
|
||||||
return now.strftime("%Y-%m-%d %H:%M:%S")
|
return now.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
|
||||||
def debug(
|
def debug(
|
||||||
*msgs: str,
|
*msgs: str,
|
||||||
level: int = logging.DEBUG,
|
level: int = logging.DEBUG,
|
||||||
fg: str = "white",
|
fg: str = 'white',
|
||||||
bg: str | None = None,
|
bg: str | None = None,
|
||||||
reset: bool = False,
|
reset: bool = False,
|
||||||
font: list[Font] = [],
|
font: list[Font] = [],
|
||||||
|
|
@ -282,7 +282,7 @@ def debug(
|
||||||
def error(
|
def error(
|
||||||
*msgs: str,
|
*msgs: str,
|
||||||
level: int = logging.ERROR,
|
level: int = logging.ERROR,
|
||||||
fg: str = "red",
|
fg: str = 'red',
|
||||||
bg: str | None = None,
|
bg: str | None = None,
|
||||||
reset: bool = False,
|
reset: bool = False,
|
||||||
font: list[Font] = [],
|
font: list[Font] = [],
|
||||||
|
|
@ -293,7 +293,7 @@ def error(
|
||||||
def warn(
|
def warn(
|
||||||
*msgs: str,
|
*msgs: str,
|
||||||
level: int = logging.WARNING,
|
level: int = logging.WARNING,
|
||||||
fg: str = "yellow",
|
fg: str = 'yellow',
|
||||||
bg: str | None = None,
|
bg: str | None = None,
|
||||||
reset: bool = False,
|
reset: bool = False,
|
||||||
font: list[Font] = [],
|
font: list[Font] = [],
|
||||||
|
|
@ -304,7 +304,7 @@ def warn(
|
||||||
def log(
|
def log(
|
||||||
*msgs: str,
|
*msgs: str,
|
||||||
level: int = logging.INFO,
|
level: int = logging.INFO,
|
||||||
fg: str = "white",
|
fg: str = 'white',
|
||||||
bg: str | None = None,
|
bg: str | None = None,
|
||||||
reset: bool = False,
|
reset: bool = False,
|
||||||
font: list[Font] = [],
|
font: list[Font] = [],
|
||||||
|
|
@ -313,19 +313,19 @@ def log(
|
||||||
# right from the beginning when the modules are loaded
|
# right from the beginning when the modules are loaded
|
||||||
_check_log_permissions()
|
_check_log_permissions()
|
||||||
|
|
||||||
text = orig_string = " ".join([str(x) for x in msgs])
|
text = orig_string = ' '.join([str(x) for x in msgs])
|
||||||
|
|
||||||
# Attempt to colorize the output if supported
|
# Attempt to colorize the output if supported
|
||||||
# Insert default colors and override with **kwargs
|
# Insert default colors and override with **kwargs
|
||||||
if _supports_color():
|
if _supports_color():
|
||||||
text = _stylize_output(text, fg, bg, reset, font)
|
text = _stylize_output(text, fg, bg, reset, font)
|
||||||
|
|
||||||
log_file = storage["LOG_PATH"] / storage["LOG_FILE"]
|
log_file = storage['LOG_PATH'] / storage['LOG_FILE']
|
||||||
|
|
||||||
with log_file.open("a") as fp:
|
with log_file.open('a') as fp:
|
||||||
ts = _timestamp()
|
ts = _timestamp()
|
||||||
level_name = logging.getLevelName(level)
|
level_name = logging.getLevelName(level)
|
||||||
out = f"[{ts}] - {level_name} - {orig_string}\n"
|
out = f'[{ts}] - {level_name} - {orig_string}\n'
|
||||||
fp.write(out)
|
fp.write(out)
|
||||||
|
|
||||||
Journald.log(text, level=level)
|
Journald.log(text, level=level)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
from .packages import find_package, find_packages, group_search, installed_package, list_available_packages, package_search, validate_package_list
|
from .packages import find_package, find_packages, group_search, installed_package, list_available_packages, package_search, validate_package_list
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"find_package",
|
'find_package',
|
||||||
"find_packages",
|
'find_packages',
|
||||||
"group_search",
|
'group_search',
|
||||||
"installed_package",
|
'installed_package',
|
||||||
"list_available_packages",
|
'list_available_packages',
|
||||||
"package_search",
|
'package_search',
|
||||||
"validate_package_list",
|
'validate_package_list',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ from ..models.packages import AvailablePackage, LocalPackage, PackageSearch, Pac
|
||||||
from ..output import debug
|
from ..output import debug
|
||||||
from ..pacman import Pacman
|
from ..pacman import Pacman
|
||||||
|
|
||||||
BASE_URL_PKG_SEARCH = "https://archlinux.org/packages/search/json/"
|
BASE_URL_PKG_SEARCH = 'https://archlinux.org/packages/search/json/'
|
||||||
# BASE_URL_PKG_CONTENT = 'https://archlinux.org/packages/search/json/'
|
# BASE_URL_PKG_CONTENT = 'https://archlinux.org/packages/search/json/'
|
||||||
BASE_GROUP_URL = "https://archlinux.org/groups/search/json/"
|
BASE_GROUP_URL = 'https://archlinux.org/groups/search/json/'
|
||||||
|
|
||||||
|
|
||||||
def _make_request(url: str, params: dict[str, str]) -> addinfourl:
|
def _make_request(url: str, params: dict[str, str]) -> addinfourl:
|
||||||
|
|
@ -22,7 +22,7 @@ def _make_request(url: str, params: dict[str, str]) -> addinfourl:
|
||||||
ssl_context.verify_mode = ssl.CERT_NONE
|
ssl_context.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
encoded = urlencode(params)
|
encoded = urlencode(params)
|
||||||
full_url = f"{url}?{encoded}"
|
full_url = f'{url}?{encoded}'
|
||||||
|
|
||||||
return urlopen(full_url, context=ssl_context)
|
return urlopen(full_url, context=ssl_context)
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ def _make_request(url: str, params: dict[str, str]) -> addinfourl:
|
||||||
def group_search(name: str) -> list[PackageSearchResult]:
|
def group_search(name: str) -> list[PackageSearchResult]:
|
||||||
# TODO UPSTREAM: Implement /json/ for the groups search
|
# TODO UPSTREAM: Implement /json/ for the groups search
|
||||||
try:
|
try:
|
||||||
response = _make_request(BASE_GROUP_URL, {"name": name})
|
response = _make_request(BASE_GROUP_URL, {'name': name})
|
||||||
except HTTPError as err:
|
except HTTPError as err:
|
||||||
if err.code == 404:
|
if err.code == 404:
|
||||||
return []
|
return []
|
||||||
|
|
@ -38,9 +38,9 @@ def group_search(name: str) -> list[PackageSearchResult]:
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
# Just to be sure some code didn't slip through the exception
|
# Just to be sure some code didn't slip through the exception
|
||||||
data = response.read().decode("utf-8")
|
data = response.read().decode('utf-8')
|
||||||
|
|
||||||
return [PackageSearchResult(**package) for package in json.loads(data)["results"]]
|
return [PackageSearchResult(**package) for package in json.loads(data)['results']]
|
||||||
|
|
||||||
|
|
||||||
def package_search(package: str) -> PackageSearch:
|
def package_search(package: str) -> PackageSearch:
|
||||||
|
|
@ -50,12 +50,12 @@ def package_search(package: str) -> PackageSearch:
|
||||||
"""
|
"""
|
||||||
# TODO UPSTREAM: Implement bulk search, either support name=X&name=Y or split on space (%20 or ' ')
|
# TODO UPSTREAM: Implement bulk search, either support name=X&name=Y or split on space (%20 or ' ')
|
||||||
# TODO: utilize pacman cache first, upstream second.
|
# TODO: utilize pacman cache first, upstream second.
|
||||||
response = _make_request(BASE_URL_PKG_SEARCH, {"name": package})
|
response = _make_request(BASE_URL_PKG_SEARCH, {'name': package})
|
||||||
|
|
||||||
if response.code != 200:
|
if response.code != 200:
|
||||||
raise PackageError(f"Could not locate package: [{response.code}] {response}")
|
raise PackageError(f'Could not locate package: [{response.code}] {response}')
|
||||||
|
|
||||||
data = response.read().decode("UTF-8")
|
data = response.read().decode('UTF-8')
|
||||||
json_data = json.loads(data)
|
json_data = json.loads(data)
|
||||||
return PackageSearch.from_json(json_data)
|
return PackageSearch.from_json(json_data)
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ def validate_package_list(packages: list[str]) -> tuple[list[str], list[str]]:
|
||||||
def installed_package(package: str) -> LocalPackage | None:
|
def installed_package(package: str) -> LocalPackage | None:
|
||||||
package_info = []
|
package_info = []
|
||||||
try:
|
try:
|
||||||
package_info = Pacman.run(f"-Q --info {package}").decode().split("\n")
|
package_info = Pacman.run(f'-Q --info {package}').decode().split('\n')
|
||||||
return _parse_package_output(package_info, LocalPackage)
|
return _parse_package_output(package_info, LocalPackage)
|
||||||
except SysCallError:
|
except SysCallError:
|
||||||
pass
|
pass
|
||||||
|
|
@ -127,15 +127,15 @@ def list_available_packages(
|
||||||
filtered_repos = [name for repo in repositories for name in repo.get_repository_list()]
|
filtered_repos = [name for repo in repositories for name in repo.get_repository_list()]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Pacman.run("-Sy")
|
Pacman.run('-Sy')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug(f"Failed to sync Arch Linux package database: {e}")
|
debug(f'Failed to sync Arch Linux package database: {e}')
|
||||||
|
|
||||||
for line in Pacman.run("-S --info"):
|
for line in Pacman.run('-S --info'):
|
||||||
dec_line = line.decode().strip()
|
dec_line = line.decode().strip()
|
||||||
current_package.append(dec_line)
|
current_package.append(dec_line)
|
||||||
|
|
||||||
if dec_line.startswith("Validated"):
|
if dec_line.startswith('Validated'):
|
||||||
if current_package:
|
if current_package:
|
||||||
avail_pkg = _parse_package_output(current_package, AvailablePackage)
|
avail_pkg = _parse_package_output(current_package, AvailablePackage)
|
||||||
if avail_pkg.repository in filtered_repos:
|
if avail_pkg.repository in filtered_repos:
|
||||||
|
|
@ -147,7 +147,7 @@ def list_available_packages(
|
||||||
|
|
||||||
@lru_cache(maxsize=128)
|
@lru_cache(maxsize=128)
|
||||||
def _normalize_key_name(key: str) -> str:
|
def _normalize_key_name(key: str) -> str:
|
||||||
return key.strip().lower().replace(" ", "_")
|
return key.strip().lower().replace(' ', '_')
|
||||||
|
|
||||||
|
|
||||||
def _parse_package_output[PackageType: (AvailablePackage, LocalPackage)](
|
def _parse_package_output[PackageType: (AvailablePackage, LocalPackage)](
|
||||||
|
|
@ -157,8 +157,8 @@ def _parse_package_output[PackageType: (AvailablePackage, LocalPackage)](
|
||||||
package = {}
|
package = {}
|
||||||
|
|
||||||
for line in package_meta:
|
for line in package_meta:
|
||||||
if ":" in line:
|
if ':' in line:
|
||||||
key, value = line.split(":", 1)
|
key, value = line.split(':', 1)
|
||||||
key = _normalize_key_name(key)
|
key = _normalize_key_name(key)
|
||||||
package[key] = value.strip()
|
package[key] = value.strip()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,26 +18,26 @@ class Pacman:
|
||||||
self.target = target
|
self.target = target
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run(args: str, default_cmd: str = "pacman") -> SysCommand:
|
def run(args: str, default_cmd: str = 'pacman') -> SysCommand:
|
||||||
"""
|
"""
|
||||||
A centralized function to call `pacman` from.
|
A centralized function to call `pacman` from.
|
||||||
It also protects us from colliding with other running pacman sessions (if used locally).
|
It also protects us from colliding with other running pacman sessions (if used locally).
|
||||||
The grace period is set to 10 minutes before exiting hard if another pacman instance is running.
|
The grace period is set to 10 minutes before exiting hard if another pacman instance is running.
|
||||||
"""
|
"""
|
||||||
pacman_db_lock = Path("/var/lib/pacman/db.lck")
|
pacman_db_lock = Path('/var/lib/pacman/db.lck')
|
||||||
|
|
||||||
if pacman_db_lock.exists():
|
if pacman_db_lock.exists():
|
||||||
warn(tr("Pacman is already running, waiting maximum 10 minutes for it to terminate."))
|
warn(tr('Pacman is already running, waiting maximum 10 minutes for it to terminate.'))
|
||||||
|
|
||||||
started = time.time()
|
started = time.time()
|
||||||
while pacman_db_lock.exists():
|
while pacman_db_lock.exists():
|
||||||
time.sleep(0.25)
|
time.sleep(0.25)
|
||||||
|
|
||||||
if time.time() - started > (60 * 10):
|
if time.time() - started > (60 * 10):
|
||||||
error(tr("Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall."))
|
error(tr('Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.'))
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
return SysCommand(f"{default_cmd} {args}")
|
return SysCommand(f'{default_cmd} {args}')
|
||||||
|
|
||||||
def ask(self, error_message: str, bail_message: str, func: Callable, *args, **kwargs) -> None: # type: ignore[type-arg]
|
def ask(self, error_message: str, bail_message: str, func: Callable, *args, **kwargs) -> None: # type: ignore[type-arg]
|
||||||
while True:
|
while True:
|
||||||
|
|
@ -45,20 +45,20 @@ class Pacman:
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
break
|
break
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
error(f"{error_message}: {err}")
|
error(f'{error_message}: {err}')
|
||||||
if not self.silent and input("Would you like to re-try this download? (Y/n): ").lower().strip() in "y":
|
if not self.silent and input('Would you like to re-try this download? (Y/n): ').lower().strip() in 'y':
|
||||||
continue
|
continue
|
||||||
raise RequirementError(f"{bail_message}: {err}")
|
raise RequirementError(f'{bail_message}: {err}')
|
||||||
|
|
||||||
def sync(self) -> None:
|
def sync(self) -> None:
|
||||||
if self.synced:
|
if self.synced:
|
||||||
return
|
return
|
||||||
self.ask(
|
self.ask(
|
||||||
"Could not sync a new package database",
|
'Could not sync a new package database',
|
||||||
"Could not sync mirrors",
|
'Could not sync mirrors',
|
||||||
self.run,
|
self.run,
|
||||||
"-Syy",
|
'-Syy',
|
||||||
default_cmd="pacman",
|
default_cmd='pacman',
|
||||||
)
|
)
|
||||||
self.synced = True
|
self.synced = True
|
||||||
|
|
||||||
|
|
@ -68,22 +68,22 @@ class Pacman:
|
||||||
packages = [packages]
|
packages = [packages]
|
||||||
|
|
||||||
for plugin in plugins.values():
|
for plugin in plugins.values():
|
||||||
if hasattr(plugin, "on_pacstrap"):
|
if hasattr(plugin, 'on_pacstrap'):
|
||||||
if result := plugin.on_pacstrap(packages):
|
if result := plugin.on_pacstrap(packages):
|
||||||
packages = result
|
packages = result
|
||||||
|
|
||||||
info(f"Installing packages: {packages}")
|
info(f'Installing packages: {packages}')
|
||||||
|
|
||||||
self.ask(
|
self.ask(
|
||||||
"Could not strap in packages",
|
'Could not strap in packages',
|
||||||
"Pacstrap failed. See /var/log/archinstall/install.log or above message for error details",
|
'Pacstrap failed. See /var/log/archinstall/install.log or above message for error details',
|
||||||
SysCommand,
|
SysCommand,
|
||||||
f"pacstrap -C /etc/pacman.conf -K {self.target} {' '.join(packages)} --noconfirm",
|
f'pacstrap -C /etc/pacman.conf -K {self.target} {" ".join(packages)} --noconfirm',
|
||||||
peek_output=True,
|
peek_output=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Pacman",
|
'Pacman',
|
||||||
"PacmanConfig",
|
'PacmanConfig',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ from ..models.packages import Repository
|
||||||
|
|
||||||
class PacmanConfig:
|
class PacmanConfig:
|
||||||
def __init__(self, target: Path | None):
|
def __init__(self, target: Path | None):
|
||||||
self._config_path = Path("/etc") / "pacman.conf"
|
self._config_path = Path('/etc') / 'pacman.conf'
|
||||||
|
|
||||||
if target:
|
if target:
|
||||||
self._config_remote_path = target / "etc" / "pacman.conf"
|
self._config_remote_path = target / 'etc' / 'pacman.conf'
|
||||||
|
|
||||||
self._repositories: list[Repository] = []
|
self._repositories: list[Repository] = []
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ class PacmanConfig:
|
||||||
repos_to_enable = []
|
repos_to_enable = []
|
||||||
for repo in self._repositories:
|
for repo in self._repositories:
|
||||||
if repo == Repository.Testing:
|
if repo == Repository.Testing:
|
||||||
repos_to_enable.extend(["core-testing", "extra-testing", "multilib-testing"])
|
repos_to_enable.extend(['core-testing', 'extra-testing', 'multilib-testing'])
|
||||||
else:
|
else:
|
||||||
repos_to_enable.append(repo.value)
|
repos_to_enable.append(repo.value)
|
||||||
|
|
||||||
|
|
@ -35,18 +35,18 @@ class PacmanConfig:
|
||||||
|
|
||||||
for row, line in enumerate(content):
|
for row, line in enumerate(content):
|
||||||
# Check if this is a commented repository section that needs to be enabled
|
# Check if this is a commented repository section that needs to be enabled
|
||||||
match = re.match(r"^#\s*\[(.*)\]", line)
|
match = re.match(r'^#\s*\[(.*)\]', line)
|
||||||
|
|
||||||
if match and match.group(1) in repos_to_enable:
|
if match and match.group(1) in repos_to_enable:
|
||||||
# uncomment the repository section line, properly removing # and any spaces
|
# uncomment the repository section line, properly removing # and any spaces
|
||||||
content[row] = re.sub(r"^#\s*", "", line)
|
content[row] = re.sub(r'^#\s*', '', line)
|
||||||
|
|
||||||
# also uncomment the next line (Include statement) if it exists and is commented
|
# also uncomment the next line (Include statement) if it exists and is commented
|
||||||
if row + 1 < len(content) and content[row + 1].lstrip().startswith("#"):
|
if row + 1 < len(content) and content[row + 1].lstrip().startswith('#'):
|
||||||
content[row + 1] = re.sub(r"^#\s*", "", content[row + 1])
|
content[row + 1] = re.sub(r'^#\s*', '', content[row + 1])
|
||||||
|
|
||||||
# Write the modified content back to the file
|
# Write the modified content back to the file
|
||||||
with open(self._config_path, "w") as f:
|
with open(self._config_path, 'w') as f:
|
||||||
f.writelines(content)
|
f.writelines(content)
|
||||||
|
|
||||||
def persist(self) -> None:
|
def persist(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,15 @@ plugins = {}
|
||||||
# 1: List archinstall.plugin definitions
|
# 1: List archinstall.plugin definitions
|
||||||
# 2: Load the plugin entrypoint
|
# 2: Load the plugin entrypoint
|
||||||
# 3: Initiate the plugin and store it as .name in plugins
|
# 3: Initiate the plugin and store it as .name in plugins
|
||||||
for plugin_definition in metadata.entry_points().select(group="archinstall.plugin"):
|
for plugin_definition in metadata.entry_points().select(group='archinstall.plugin'):
|
||||||
plugin_entrypoint = plugin_definition.load()
|
plugin_entrypoint = plugin_definition.load()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
plugins[plugin_definition.name] = plugin_entrypoint()
|
plugins[plugin_definition.name] = plugin_entrypoint()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
error(
|
error(
|
||||||
f"Error: {err}",
|
f'Error: {err}',
|
||||||
f"The above error was detected when loading the plugin: {plugin_definition}",
|
f'The above error was detected when loading the plugin: {plugin_definition}',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -34,11 +34,11 @@ def _localize_path(path: Path) -> Path:
|
||||||
"""
|
"""
|
||||||
url = urllib.parse.urlparse(str(path))
|
url = urllib.parse.urlparse(str(path))
|
||||||
|
|
||||||
if url.scheme and url.scheme in ("https", "http"):
|
if url.scheme and url.scheme in ('https', 'http'):
|
||||||
converted_path = Path(f"/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py")
|
converted_path = Path(f'/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py')
|
||||||
|
|
||||||
with open(converted_path, "w") as temp_file:
|
with open(converted_path, 'w') as temp_file:
|
||||||
temp_file.write(urllib.request.urlopen(url.geturl()).read().decode("utf-8"))
|
temp_file.write(urllib.request.urlopen(url.geturl()).read().decode('utf-8'))
|
||||||
|
|
||||||
return converted_path
|
return converted_path
|
||||||
else:
|
else:
|
||||||
|
|
@ -49,7 +49,7 @@ def _import_via_path(path: Path, namespace: str | None = None) -> str | None:
|
||||||
if not namespace:
|
if not namespace:
|
||||||
namespace = os.path.basename(path)
|
namespace = os.path.basename(path)
|
||||||
|
|
||||||
if namespace == "__init__.py":
|
if namespace == '__init__.py':
|
||||||
namespace = path.parent.name
|
namespace = path.parent.name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -62,8 +62,8 @@ def _import_via_path(path: Path, namespace: str | None = None) -> str | None:
|
||||||
return namespace
|
return namespace
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
error(
|
error(
|
||||||
f"Error: {err}",
|
f'Error: {err}',
|
||||||
f"The above error was detected when loading the plugin: {path}",
|
f'The above error was detected when loading the plugin: {path}',
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -84,36 +84,36 @@ def _find_nth(haystack: list[str], needle: str, n: int) -> int | None:
|
||||||
def load_plugin(path: Path) -> None:
|
def load_plugin(path: Path) -> None:
|
||||||
namespace: str | None = None
|
namespace: str | None = None
|
||||||
parsed_url = urllib.parse.urlparse(str(path))
|
parsed_url = urllib.parse.urlparse(str(path))
|
||||||
info(f"Loading plugin from url {parsed_url}")
|
info(f'Loading plugin from url {parsed_url}')
|
||||||
|
|
||||||
# The Profile was not a direct match on a remote URL
|
# The Profile was not a direct match on a remote URL
|
||||||
if not parsed_url.scheme:
|
if not parsed_url.scheme:
|
||||||
# Path was not found in any known examples, check if it's an absolute path
|
# Path was not found in any known examples, check if it's an absolute path
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
namespace = _import_via_path(path)
|
namespace = _import_via_path(path)
|
||||||
elif parsed_url.scheme in ("https", "http"):
|
elif parsed_url.scheme in ('https', 'http'):
|
||||||
localized = _localize_path(path)
|
localized = _localize_path(path)
|
||||||
namespace = _import_via_path(localized)
|
namespace = _import_via_path(localized)
|
||||||
|
|
||||||
if namespace and namespace in sys.modules:
|
if namespace and namespace in sys.modules:
|
||||||
# Version dependency via __archinstall__version__ variable (if present) in the plugin
|
# Version dependency via __archinstall__version__ variable (if present) in the plugin
|
||||||
# Any errors in version inconsistency will be handled through normal error handling if not defined.
|
# Any errors in version inconsistency will be handled through normal error handling if not defined.
|
||||||
if hasattr(sys.modules[namespace], "__archinstall__version__"):
|
if hasattr(sys.modules[namespace], '__archinstall__version__'):
|
||||||
archinstall_major_and_minor_version = float(storage["__version__"][: _find_nth(storage["__version__"], ".", 2)])
|
archinstall_major_and_minor_version = float(storage['__version__'][: _find_nth(storage['__version__'], '.', 2)])
|
||||||
|
|
||||||
if sys.modules[namespace].__archinstall__version__ < archinstall_major_and_minor_version:
|
if sys.modules[namespace].__archinstall__version__ < archinstall_major_and_minor_version:
|
||||||
error(f"Plugin {sys.modules[namespace]} does not support the current Archinstall version.")
|
error(f'Plugin {sys.modules[namespace]} does not support the current Archinstall version.')
|
||||||
|
|
||||||
# Locate the plugin entry-point called Plugin()
|
# Locate the plugin entry-point called Plugin()
|
||||||
# This in accordance with the entry_points() from setup.cfg above
|
# This in accordance with the entry_points() from setup.cfg above
|
||||||
if hasattr(sys.modules[namespace], "Plugin"):
|
if hasattr(sys.modules[namespace], 'Plugin'):
|
||||||
try:
|
try:
|
||||||
plugins[namespace] = sys.modules[namespace].Plugin()
|
plugins[namespace] = sys.modules[namespace].Plugin()
|
||||||
info(f"Plugin {plugins[namespace]} has been loaded.")
|
info(f'Plugin {plugins[namespace]} has been loaded.')
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
error(
|
error(
|
||||||
f"Error: {err}",
|
f'Error: {err}',
|
||||||
f"The above error was detected when initiating the plugin: {path}",
|
f'The above error was detected when initiating the plugin: {path}',
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
warn(f"Plugin '{path}' is missing a valid entry-point or is corrupt.")
|
warn(f"Plugin '{path}' is missing a valid entry-point or is corrupt.")
|
||||||
|
|
|
||||||
|
|
@ -37,29 +37,29 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
||||||
def _define_menu_options(self) -> list[MenuItem]:
|
def _define_menu_options(self) -> list[MenuItem]:
|
||||||
return [
|
return [
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Type"),
|
text=tr('Type'),
|
||||||
action=self._select_profile,
|
action=self._select_profile,
|
||||||
value=self._profile_config.profile,
|
value=self._profile_config.profile,
|
||||||
preview_action=self._preview_profile,
|
preview_action=self._preview_profile,
|
||||||
key="profile",
|
key='profile',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Graphics driver"),
|
text=tr('Graphics driver'),
|
||||||
action=self._select_gfx_driver,
|
action=self._select_gfx_driver,
|
||||||
value=self._profile_config.gfx_driver if self._profile_config.profile and self._profile_config.profile.is_graphic_driver_supported() else None,
|
value=self._profile_config.gfx_driver if self._profile_config.profile and self._profile_config.profile.is_graphic_driver_supported() else None,
|
||||||
preview_action=self._prev_gfx,
|
preview_action=self._prev_gfx,
|
||||||
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
|
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
|
||||||
dependencies=["profile"],
|
dependencies=['profile'],
|
||||||
key="gfx_driver",
|
key='gfx_driver',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr("Greeter"),
|
text=tr('Greeter'),
|
||||||
action=lambda x: select_greeter(preset=x),
|
action=lambda x: select_greeter(preset=x),
|
||||||
value=self._profile_config.greeter if self._profile_config.profile and self._profile_config.profile.is_greeter_supported() else None,
|
value=self._profile_config.greeter if self._profile_config.profile and self._profile_config.profile.is_greeter_supported() else None,
|
||||||
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
|
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
|
||||||
preview_action=self._prev_greeter,
|
preview_action=self._prev_greeter,
|
||||||
dependencies=["profile"],
|
dependencies=['profile'],
|
||||||
key="greeter",
|
key='greeter',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -73,36 +73,36 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
||||||
|
|
||||||
if profile is not None:
|
if profile is not None:
|
||||||
if not profile.is_graphic_driver_supported():
|
if not profile.is_graphic_driver_supported():
|
||||||
self._item_group.find_by_key("gfx_driver").enabled = False
|
self._item_group.find_by_key('gfx_driver').enabled = False
|
||||||
self._item_group.find_by_key("gfx_driver").value = None
|
self._item_group.find_by_key('gfx_driver').value = None
|
||||||
else:
|
else:
|
||||||
self._item_group.find_by_key("gfx_driver").enabled = True
|
self._item_group.find_by_key('gfx_driver').enabled = True
|
||||||
self._item_group.find_by_key("gfx_driver").value = GfxDriver.AllOpenSource
|
self._item_group.find_by_key('gfx_driver').value = GfxDriver.AllOpenSource
|
||||||
|
|
||||||
if not profile.is_greeter_supported():
|
if not profile.is_greeter_supported():
|
||||||
self._item_group.find_by_key("greeter").enabled = False
|
self._item_group.find_by_key('greeter').enabled = False
|
||||||
self._item_group.find_by_key("greeter").value = None
|
self._item_group.find_by_key('greeter').value = None
|
||||||
else:
|
else:
|
||||||
self._item_group.find_by_key("greeter").enabled = True
|
self._item_group.find_by_key('greeter').enabled = True
|
||||||
self._item_group.find_by_key("greeter").value = profile.default_greeter_type
|
self._item_group.find_by_key('greeter').value = profile.default_greeter_type
|
||||||
else:
|
else:
|
||||||
self._item_group.find_by_key("gfx_driver").value = None
|
self._item_group.find_by_key('gfx_driver').value = None
|
||||||
self._item_group.find_by_key("greeter").value = None
|
self._item_group.find_by_key('greeter').value = None
|
||||||
|
|
||||||
return profile
|
return profile
|
||||||
|
|
||||||
def _select_gfx_driver(self, preset: GfxDriver | None = None) -> GfxDriver | None:
|
def _select_gfx_driver(self, preset: GfxDriver | None = None) -> GfxDriver | None:
|
||||||
driver = preset
|
driver = preset
|
||||||
profile: Profile | None = self._item_group.find_by_key("profile").value
|
profile: Profile | None = self._item_group.find_by_key('profile').value
|
||||||
|
|
||||||
if profile:
|
if profile:
|
||||||
if profile.is_graphic_driver_supported():
|
if profile.is_graphic_driver_supported():
|
||||||
driver = select_driver(preset=preset)
|
driver = select_driver(preset=preset)
|
||||||
|
|
||||||
if driver and "Sway" in profile.current_selection_names():
|
if driver and 'Sway' in profile.current_selection_names():
|
||||||
if driver.is_nvidia():
|
if driver.is_nvidia():
|
||||||
header = tr("The proprietary Nvidia driver is not supported by Sway.") + "\n"
|
header = tr('The proprietary Nvidia driver is not supported by Sway.') + '\n'
|
||||||
header += tr("It is likely that you will run into issues, are you okay with that?") + "\n"
|
header += tr('It is likely that you will run into issues, are you okay with that?') + '\n'
|
||||||
|
|
||||||
group = MenuItemGroup.yes_no()
|
group = MenuItemGroup.yes_no()
|
||||||
group.focus_item = MenuItem.no()
|
group.focus_item = MenuItem.no()
|
||||||
|
|
@ -126,25 +126,25 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
||||||
if item.value:
|
if item.value:
|
||||||
driver = item.get_value().value
|
driver = item.get_value().value
|
||||||
packages = item.get_value().packages_text()
|
packages = item.get_value().packages_text()
|
||||||
return f"Driver: {driver}\n{packages}"
|
return f'Driver: {driver}\n{packages}'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prev_greeter(self, item: MenuItem) -> str | None:
|
def _prev_greeter(self, item: MenuItem) -> str | None:
|
||||||
if item.value:
|
if item.value:
|
||||||
return f"{tr('Greeter')}: {item.value.value}"
|
return f'{tr("Greeter")}: {item.value.value}'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _preview_profile(self, item: MenuItem) -> str | None:
|
def _preview_profile(self, item: MenuItem) -> str | None:
|
||||||
profile: Profile | None = item.value
|
profile: Profile | None = item.value
|
||||||
text = ""
|
text = ''
|
||||||
|
|
||||||
if profile:
|
if profile:
|
||||||
if (sub_profiles := profile.current_selection) is not None:
|
if (sub_profiles := profile.current_selection) is not None:
|
||||||
text += tr("Selected profiles: ")
|
text += tr('Selected profiles: ')
|
||||||
text += ", ".join([p.name for p in sub_profiles]) + "\n"
|
text += ', '.join([p.name for p in sub_profiles]) + '\n'
|
||||||
|
|
||||||
if packages := profile.packages_text(include_sub_packages=True):
|
if packages := profile.packages_text(include_sub_packages=True):
|
||||||
text += f"{packages}"
|
text += f'{packages}'
|
||||||
|
|
||||||
if text:
|
if text:
|
||||||
return text
|
return text
|
||||||
|
|
@ -172,7 +172,7 @@ def select_greeter(
|
||||||
result = SelectMenu[GreeterType](
|
result = SelectMenu[GreeterType](
|
||||||
group,
|
group,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
frame=FrameProperties.min(tr("Greeter")),
|
frame=FrameProperties.min(tr('Greeter')),
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
|
@ -182,7 +182,7 @@ def select_greeter(
|
||||||
case ResultType.Selection:
|
case ResultType.Selection:
|
||||||
return result.get_value()
|
return result.get_value()
|
||||||
case ResultType.Reset:
|
case ResultType.Reset:
|
||||||
raise ValueError("Unhandled result type")
|
raise ValueError('Unhandled result type')
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -197,7 +197,7 @@ def select_profile(
|
||||||
top_level_profiles = profile_handler.get_top_level_profiles()
|
top_level_profiles = profile_handler.get_top_level_profiles()
|
||||||
|
|
||||||
if header is None:
|
if header is None:
|
||||||
header = tr("This is a list of pre-programmed default_profiles") + "\n"
|
header = tr('This is a list of pre-programmed default_profiles') + '\n'
|
||||||
|
|
||||||
items = [MenuItem(p.name, value=p) for p in top_level_profiles]
|
items = [MenuItem(p.name, value=p) for p in top_level_profiles]
|
||||||
group = MenuItemGroup(items, sort_items=True)
|
group = MenuItemGroup(items, sort_items=True)
|
||||||
|
|
@ -209,7 +209,7 @@ def select_profile(
|
||||||
allow_reset=allow_reset,
|
allow_reset=allow_reset,
|
||||||
allow_skip=True,
|
allow_skip=True,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
frame=FrameProperties.min(tr("Main profile")),
|
frame=FrameProperties.min(tr('Main profile')),
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
match result.type_:
|
match result.type_:
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,13 @@ class ProfileHandler:
|
||||||
|
|
||||||
if profile is not None:
|
if profile is not None:
|
||||||
data = {
|
data = {
|
||||||
"main": profile.name,
|
'main': profile.name,
|
||||||
"details": [profile.name for profile in profile.current_selection],
|
'details': [profile.name for profile in profile.current_selection],
|
||||||
"custom_settings": {profile.name: profile.custom_settings for profile in profile.current_selection},
|
'custom_settings': {profile.name: profile.custom_settings for profile in profile.current_selection},
|
||||||
}
|
}
|
||||||
|
|
||||||
if self._url_path is not None:
|
if self._url_path is not None:
|
||||||
data["path"] = self._url_path
|
data['path'] = self._url_path
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ class ProfileHandler:
|
||||||
# load all the default_profiles from url and custom
|
# load all the default_profiles from url and custom
|
||||||
# so that we can then apply whatever was specified
|
# so that we can then apply whatever was specified
|
||||||
# in the main/detail sections
|
# in the main/detail sections
|
||||||
if url_path := profile_config.get("path", None):
|
if url_path := profile_config.get('path', None):
|
||||||
self._url_path = url_path
|
self._url_path = url_path
|
||||||
local_path = Path(url_path)
|
local_path = Path(url_path)
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ class ProfileHandler:
|
||||||
# if custom_profile := self.get_profile_by_name('Custom'):
|
# if custom_profile := self.get_profile_by_name('Custom'):
|
||||||
# custom_profile.set_current_selection(custom_types)
|
# custom_profile.set_current_selection(custom_types)
|
||||||
|
|
||||||
if main := profile_config.get("main", None):
|
if main := profile_config.get('main', None):
|
||||||
profile = self.get_profile_by_name(main) if main else None
|
profile = self.get_profile_by_name(main) if main else None
|
||||||
|
|
||||||
if not profile:
|
if not profile:
|
||||||
|
|
@ -108,14 +108,14 @@ class ProfileHandler:
|
||||||
|
|
||||||
valid_sub_profiles: list[Profile] = []
|
valid_sub_profiles: list[Profile] = []
|
||||||
invalid_sub_profiles: list[str] = []
|
invalid_sub_profiles: list[str] = []
|
||||||
details: list[str] = profile_config.get("details", [])
|
details: list[str] = profile_config.get('details', [])
|
||||||
|
|
||||||
if details:
|
if details:
|
||||||
for detail in filter(None, details):
|
for detail in filter(None, details):
|
||||||
# [2024-04-19] TODO: Backwards compatibility after naming change: https://github.com/archlinux/archinstall/pull/2421
|
# [2024-04-19] TODO: Backwards compatibility after naming change: https://github.com/archlinux/archinstall/pull/2421
|
||||||
# 'Kde' is deprecated, remove this block in a future version
|
# 'Kde' is deprecated, remove this block in a future version
|
||||||
if detail == "Kde":
|
if detail == 'Kde':
|
||||||
detail = "KDE Plasma"
|
detail = 'KDE Plasma'
|
||||||
|
|
||||||
if sub_profile := self.get_profile_by_name(detail):
|
if sub_profile := self.get_profile_by_name(detail):
|
||||||
valid_sub_profiles.append(sub_profile)
|
valid_sub_profiles.append(sub_profile)
|
||||||
|
|
@ -123,9 +123,9 @@ class ProfileHandler:
|
||||||
invalid_sub_profiles.append(detail)
|
invalid_sub_profiles.append(detail)
|
||||||
|
|
||||||
if invalid_sub_profiles:
|
if invalid_sub_profiles:
|
||||||
info("No profile definition found: {}".format(", ".join(invalid_sub_profiles)))
|
info('No profile definition found: {}'.format(', '.join(invalid_sub_profiles)))
|
||||||
|
|
||||||
custom_settings = profile_config.get("custom_settings", {})
|
custom_settings = profile_config.get('custom_settings', {})
|
||||||
profile.current_selection = valid_sub_profiles
|
profile.current_selection = valid_sub_profiles
|
||||||
|
|
||||||
for sub_profile in valid_sub_profiles:
|
for sub_profile in valid_sub_profiles:
|
||||||
|
|
@ -182,28 +182,28 @@ class ProfileHandler:
|
||||||
tailored = [p for p in self.profiles if p.is_tailored()]
|
tailored = [p for p in self.profiles if p.is_tailored()]
|
||||||
return [t for t in tailored if t.name in self._local_mac_addresses]
|
return [t for t in tailored if t.name in self._local_mac_addresses]
|
||||||
|
|
||||||
def install_greeter(self, install_session: "Installer", greeter: GreeterType) -> None:
|
def install_greeter(self, install_session: 'Installer', greeter: GreeterType) -> None:
|
||||||
packages = []
|
packages = []
|
||||||
service = None
|
service = None
|
||||||
|
|
||||||
match greeter:
|
match greeter:
|
||||||
case GreeterType.LightdmSlick:
|
case GreeterType.LightdmSlick:
|
||||||
packages = ["lightdm", "lightdm-slick-greeter"]
|
packages = ['lightdm', 'lightdm-slick-greeter']
|
||||||
service = ["lightdm"]
|
service = ['lightdm']
|
||||||
case GreeterType.Lightdm:
|
case GreeterType.Lightdm:
|
||||||
packages = ["lightdm", "lightdm-gtk-greeter"]
|
packages = ['lightdm', 'lightdm-gtk-greeter']
|
||||||
service = ["lightdm"]
|
service = ['lightdm']
|
||||||
case GreeterType.Sddm:
|
case GreeterType.Sddm:
|
||||||
packages = ["sddm"]
|
packages = ['sddm']
|
||||||
service = ["sddm"]
|
service = ['sddm']
|
||||||
case GreeterType.Gdm:
|
case GreeterType.Gdm:
|
||||||
packages = ["gdm"]
|
packages = ['gdm']
|
||||||
service = ["gdm"]
|
service = ['gdm']
|
||||||
case GreeterType.Ly:
|
case GreeterType.Ly:
|
||||||
packages = ["ly"]
|
packages = ['ly']
|
||||||
service = ["ly"]
|
service = ['ly']
|
||||||
case GreeterType.CosmicSession:
|
case GreeterType.CosmicSession:
|
||||||
packages = ["cosmic-greeter"]
|
packages = ['cosmic-greeter']
|
||||||
|
|
||||||
if packages:
|
if packages:
|
||||||
install_session.add_additional_packages(packages)
|
install_session.add_additional_packages(packages)
|
||||||
|
|
@ -212,35 +212,35 @@ class ProfileHandler:
|
||||||
|
|
||||||
# slick-greeter requires a config change
|
# slick-greeter requires a config change
|
||||||
if greeter == GreeterType.LightdmSlick:
|
if greeter == GreeterType.LightdmSlick:
|
||||||
path = install_session.target.joinpath("etc/lightdm/lightdm.conf")
|
path = install_session.target.joinpath('etc/lightdm/lightdm.conf')
|
||||||
with open(path) as file:
|
with open(path) as file:
|
||||||
filedata = file.read()
|
filedata = file.read()
|
||||||
|
|
||||||
filedata = filedata.replace("#greeter-session=example-gtk-gnome", "greeter-session=lightdm-slick-greeter")
|
filedata = filedata.replace('#greeter-session=example-gtk-gnome', 'greeter-session=lightdm-slick-greeter')
|
||||||
|
|
||||||
with open(path, "w") as file:
|
with open(path, 'w') as file:
|
||||||
file.write(filedata)
|
file.write(filedata)
|
||||||
|
|
||||||
def install_gfx_driver(self, install_session: "Installer", driver: GfxDriver) -> None:
|
def install_gfx_driver(self, install_session: 'Installer', driver: GfxDriver) -> None:
|
||||||
debug(f"Installing GFX driver: {driver.value}")
|
debug(f'Installing GFX driver: {driver.value}')
|
||||||
|
|
||||||
if driver in [GfxDriver.NvidiaOpenKernel, GfxDriver.NvidiaProprietary]:
|
if driver in [GfxDriver.NvidiaOpenKernel, GfxDriver.NvidiaProprietary]:
|
||||||
headers = [f"{kernel}-headers" for kernel in install_session.kernels]
|
headers = [f'{kernel}-headers' for kernel in install_session.kernels]
|
||||||
# Fixes https://github.com/archlinux/archinstall/issues/585
|
# Fixes https://github.com/archlinux/archinstall/issues/585
|
||||||
install_session.add_additional_packages(headers)
|
install_session.add_additional_packages(headers)
|
||||||
elif driver in [GfxDriver.AllOpenSource, GfxDriver.AmdOpenSource]:
|
elif driver in [GfxDriver.AllOpenSource, GfxDriver.AmdOpenSource]:
|
||||||
# The order of these two are important if amdgpu is installed #808
|
# The order of these two are important if amdgpu is installed #808
|
||||||
install_session.remove_mod("amdgpu")
|
install_session.remove_mod('amdgpu')
|
||||||
install_session.remove_mod("radeon")
|
install_session.remove_mod('radeon')
|
||||||
|
|
||||||
install_session.append_mod("amdgpu")
|
install_session.append_mod('amdgpu')
|
||||||
install_session.append_mod("radeon")
|
install_session.append_mod('radeon')
|
||||||
|
|
||||||
driver_pkgs = driver.gfx_packages()
|
driver_pkgs = driver.gfx_packages()
|
||||||
pkg_names = [p.value for p in driver_pkgs]
|
pkg_names = [p.value for p in driver_pkgs]
|
||||||
install_session.add_additional_packages(pkg_names)
|
install_session.add_additional_packages(pkg_names)
|
||||||
|
|
||||||
def install_profile_config(self, install_session: "Installer", profile_config: ProfileConfiguration) -> None:
|
def install_profile_config(self, install_session: 'Installer', profile_config: ProfileConfiguration) -> None:
|
||||||
profile = profile_config.profile
|
profile = profile_config.profile
|
||||||
|
|
||||||
if not profile:
|
if not profile:
|
||||||
|
|
@ -260,9 +260,9 @@ class ProfileHandler:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
data = fetch_data_from_url(url)
|
data = fetch_data_from_url(url)
|
||||||
b_data = bytes(data, "utf-8")
|
b_data = bytes(data, 'utf-8')
|
||||||
|
|
||||||
with NamedTemporaryFile(delete=False, suffix=".py") as fp:
|
with NamedTemporaryFile(delete=False, suffix='.py') as fp:
|
||||||
fp.write(b_data)
|
fp.write(b_data)
|
||||||
filepath = Path(fp.name)
|
filepath = Path(fp.name)
|
||||||
|
|
||||||
|
|
@ -270,7 +270,7 @@ class ProfileHandler:
|
||||||
self.remove_custom_profiles(profiles)
|
self.remove_custom_profiles(profiles)
|
||||||
self.add_custom_profiles(profiles)
|
self.add_custom_profiles(profiles)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
err = tr("Unable to fetch profile from specified url: {}").format(url)
|
err = tr('Unable to fetch profile from specified url: {}').format(url)
|
||||||
error(err)
|
error(err)
|
||||||
|
|
||||||
def _load_profile_class(self, module: ModuleType) -> list[Profile]:
|
def _load_profile_class(self, module: ModuleType) -> list[Profile]:
|
||||||
|
|
@ -288,7 +288,7 @@ class ProfileHandler:
|
||||||
if isinstance(cls_, Profile):
|
if isinstance(cls_, Profile):
|
||||||
profiles.append(cls_)
|
profiles.append(cls_)
|
||||||
except Exception:
|
except Exception:
|
||||||
debug(f"Cannot import {module}, it does not appear to be a Profile class")
|
debug(f'Cannot import {module}, it does not appear to be a Profile class')
|
||||||
|
|
||||||
return profiles
|
return profiles
|
||||||
|
|
||||||
|
|
@ -301,7 +301,7 @@ class ProfileHandler:
|
||||||
duplicates = [x for x in counter.items() if x[1] != 1]
|
duplicates = [x for x in counter.items() if x[1] != 1]
|
||||||
|
|
||||||
if len(duplicates) > 0:
|
if len(duplicates) > 0:
|
||||||
err = tr("Profiles must have unique name, but profile definitions with duplicate name found: {}").format(duplicates[0][0])
|
err = tr('Profiles must have unique name, but profile definitions with duplicate name found: {}').format(duplicates[0][0])
|
||||||
error(err)
|
error(err)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
@ -312,7 +312,7 @@ class ProfileHandler:
|
||||||
"""
|
"""
|
||||||
with open(file) as fp:
|
with open(file) as fp:
|
||||||
for line in fp.readlines():
|
for line in fp.readlines():
|
||||||
if "__packages__" in line:
|
if '__packages__' in line:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -321,15 +321,15 @@ class ProfileHandler:
|
||||||
Process a file for profile definitions
|
Process a file for profile definitions
|
||||||
"""
|
"""
|
||||||
if self._is_legacy(file):
|
if self._is_legacy(file):
|
||||||
info(f"Cannot import {file} because it is no longer supported, please use the new profile format")
|
info(f'Cannot import {file} because it is no longer supported, please use the new profile format')
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if not file.is_file():
|
if not file.is_file():
|
||||||
info(f"Cannot find profile file {file}")
|
info(f'Cannot find profile file {file}')
|
||||||
return []
|
return []
|
||||||
|
|
||||||
name = file.name.removesuffix(file.suffix)
|
name = file.name.removesuffix(file.suffix)
|
||||||
debug(f"Importing profile: {file}")
|
debug(f'Importing profile: {file}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if spec := importlib.util.spec_from_file_location(name, file):
|
if spec := importlib.util.spec_from_file_location(name, file):
|
||||||
|
|
@ -338,7 +338,7 @@ class ProfileHandler:
|
||||||
spec.loader.exec_module(imported)
|
spec.loader.exec_module(imported)
|
||||||
return self._load_profile_class(imported)
|
return self._load_profile_class(imported)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error(f"Unable to parse file {file}: {e}")
|
error(f'Unable to parse file {file}: {e}')
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
@ -346,11 +346,11 @@ class ProfileHandler:
|
||||||
"""
|
"""
|
||||||
Search the profile path for profile definitions
|
Search the profile path for profile definitions
|
||||||
"""
|
"""
|
||||||
profiles_path = Path(__file__).parents[2] / "default_profiles"
|
profiles_path = Path(__file__).parents[2] / 'default_profiles'
|
||||||
profiles = []
|
profiles = []
|
||||||
for file in profiles_path.glob("**/*.py"):
|
for file in profiles_path.glob('**/*.py'):
|
||||||
# ignore the abstract default_profiles class
|
# ignore the abstract default_profiles class
|
||||||
if "profile.py" in file.name:
|
if 'profile.py' in file.name:
|
||||||
continue
|
continue
|
||||||
profiles += self._process_profile_file(file)
|
profiles += self._process_profile_file(file)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@ from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
storage: dict[str, Any] = {
|
storage: dict[str, Any] = {
|
||||||
"LOG_PATH": Path("/var/log/archinstall"),
|
'LOG_PATH': Path('/var/log/archinstall'),
|
||||||
"LOG_FILE": Path("install.log"),
|
'LOG_FILE': Path('install.log'),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class Language:
|
||||||
@property
|
@property
|
||||||
def display_name(self) -> str:
|
def display_name(self) -> str:
|
||||||
name = self.name_en
|
name = self.name_en
|
||||||
return f"{name} ({self.translation_percent}%)"
|
return f'{name} ({self.translation_percent}%)'
|
||||||
|
|
||||||
def is_match(self, lang_or_translated_lang: str) -> bool:
|
def is_match(self, lang_or_translated_lang: str) -> bool:
|
||||||
if self.name_en == lang_or_translated_lang:
|
if self.name_en == lang_or_translated_lang:
|
||||||
|
|
@ -35,8 +35,8 @@ class Language:
|
||||||
|
|
||||||
class TranslationHandler:
|
class TranslationHandler:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._base_pot = "base.pot"
|
self._base_pot = 'base.pot'
|
||||||
self._languages = "languages.json"
|
self._languages = 'languages.json'
|
||||||
|
|
||||||
self._total_messages = self._get_total_active_messages()
|
self._total_messages = self._get_total_active_messages()
|
||||||
self._translated_languages = self._get_translations()
|
self._translated_languages = self._get_translations()
|
||||||
|
|
@ -55,17 +55,17 @@ class TranslationHandler:
|
||||||
languages = []
|
languages = []
|
||||||
|
|
||||||
for short_form in defined_languages:
|
for short_form in defined_languages:
|
||||||
mapping_entry: dict[str, str] = next(filter(lambda x: x["abbr"] == short_form, mappings))
|
mapping_entry: dict[str, str] = next(filter(lambda x: x['abbr'] == short_form, mappings))
|
||||||
abbr = mapping_entry["abbr"]
|
abbr = mapping_entry['abbr']
|
||||||
lang = mapping_entry["lang"]
|
lang = mapping_entry['lang']
|
||||||
translated_lang = mapping_entry.get("translated_lang", None)
|
translated_lang = mapping_entry.get('translated_lang', None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# get a translation for a specific language
|
# get a translation for a specific language
|
||||||
translation = gettext.translation("base", localedir=self._get_locales_dir(), languages=(abbr, lang))
|
translation = gettext.translation('base', localedir=self._get_locales_dir(), languages=(abbr, lang))
|
||||||
|
|
||||||
# calculate the percentage of total translated text to total number of messages
|
# calculate the percentage of total translated text to total number of messages
|
||||||
if abbr == "en":
|
if abbr == 'en':
|
||||||
percent = 100
|
percent = 100
|
||||||
else:
|
else:
|
||||||
num_translations = self._get_catalog_size(translation)
|
num_translations = self._get_catalog_size(translation)
|
||||||
|
|
@ -105,9 +105,9 @@ class TranslationHandler:
|
||||||
Get total messages that could be translated
|
Get total messages that could be translated
|
||||||
"""
|
"""
|
||||||
locales = self._get_locales_dir()
|
locales = self._get_locales_dir()
|
||||||
with open(f"{locales}/{self._base_pot}") as fp:
|
with open(f'{locales}/{self._base_pot}') as fp:
|
||||||
lines = fp.readlines()
|
lines = fp.readlines()
|
||||||
msgid_lines = [line for line in lines if "msgid" in line]
|
msgid_lines = [line for line in lines if 'msgid' in line]
|
||||||
|
|
||||||
return len(msgid_lines) - 1 # don't count the first line which contains the metadata
|
return len(msgid_lines) - 1 # don't count the first line which contains the metadata
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ class TranslationHandler:
|
||||||
try:
|
try:
|
||||||
return next(filter(lambda x: x.name_en == name, self._translated_languages))
|
return next(filter(lambda x: x.name_en == name, self._translated_languages))
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError(f"No language with name found: {name}")
|
raise ValueError(f'No language with name found: {name}')
|
||||||
|
|
||||||
def get_language_by_abbr(self, abbr: str) -> Language:
|
def get_language_by_abbr(self, abbr: str) -> Language:
|
||||||
"""
|
"""
|
||||||
|
|
@ -141,7 +141,7 @@ class TranslationHandler:
|
||||||
Get the locales directory path
|
Get the locales directory path
|
||||||
"""
|
"""
|
||||||
cur_path = Path(__file__).parent.parent
|
cur_path = Path(__file__).parent.parent
|
||||||
locales_dir = Path.joinpath(cur_path, "locales")
|
locales_dir = Path.joinpath(cur_path, 'locales')
|
||||||
return locales_dir
|
return locales_dir
|
||||||
|
|
||||||
def _provided_translations(self) -> list[str]:
|
def _provided_translations(self) -> list[str]:
|
||||||
|
|
@ -153,7 +153,7 @@ class TranslationHandler:
|
||||||
|
|
||||||
translation_files = []
|
translation_files = []
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
if len(filename) == 2 or filename in ["pt_BR", "zh-CN", "zh-TW"]:
|
if len(filename) == 2 or filename in ['pt_BR', 'zh-CN', 'zh-TW']:
|
||||||
translation_files.append(filename)
|
translation_files.append(filename)
|
||||||
|
|
||||||
return translation_files
|
return translation_files
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from functools import lru_cache
|
||||||
|
|
||||||
@lru_cache(maxsize=128)
|
@lru_cache(maxsize=128)
|
||||||
def _is_wide_character(char: str) -> bool:
|
def _is_wide_character(char: str) -> bool:
|
||||||
return unicodedata.east_asian_width(char) in "FW"
|
return unicodedata.east_asian_width(char) in 'FW'
|
||||||
|
|
||||||
|
|
||||||
def _count_wchars(string: str) -> int:
|
def _count_wchars(string: str) -> int:
|
||||||
|
|
@ -12,7 +12,7 @@ def _count_wchars(string: str) -> int:
|
||||||
return sum(_is_wide_character(c) for c in string)
|
return sum(_is_wide_character(c) for c in string)
|
||||||
|
|
||||||
|
|
||||||
def unicode_ljust(string: str, width: int, fillbyte: str = " ") -> str:
|
def unicode_ljust(string: str, width: int, fillbyte: str = ' ') -> str:
|
||||||
"""Return a left-justified unicode string of length width.
|
"""Return a left-justified unicode string of length width.
|
||||||
>>> unicode_ljust('Hello', 15, '*')
|
>>> unicode_ljust('Hello', 15, '*')
|
||||||
'Hello**********'
|
'Hello**********'
|
||||||
|
|
@ -26,7 +26,7 @@ def unicode_ljust(string: str, width: int, fillbyte: str = " ") -> str:
|
||||||
return string.ljust(width - _count_wchars(string), fillbyte)
|
return string.ljust(width - _count_wchars(string), fillbyte)
|
||||||
|
|
||||||
|
|
||||||
def unicode_rjust(string: str, width: int, fillbyte: str = " ") -> str:
|
def unicode_rjust(string: str, width: int, fillbyte: str = ' ') -> str:
|
||||||
"""Return a right-justified unicode string of length width.
|
"""Return a right-justified unicode string of length width.
|
||||||
>>> unicode_rjust('Hello', 15, '*')
|
>>> unicode_rjust('Hello', 15, '*')
|
||||||
'**********Hello'
|
'**********Hello'
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ def get_password(
|
||||||
while True:
|
while True:
|
||||||
user_hdr = None
|
user_hdr = None
|
||||||
if failure is not None:
|
if failure is not None:
|
||||||
user_hdr = f"{header}\n{failure}\n"
|
user_hdr = f'{header}\n{failure}\n'
|
||||||
elif header is not None:
|
elif header is not None:
|
||||||
user_hdr = header
|
user_hdr = header
|
||||||
|
|
||||||
|
|
@ -42,12 +42,12 @@ def get_password(
|
||||||
return password
|
return password
|
||||||
|
|
||||||
if header is not None:
|
if header is not None:
|
||||||
confirmation_header = f"{header}{tr('Password')}: {password.hidden()}\n"
|
confirmation_header = f'{header}{tr("Password")}: {password.hidden()}\n'
|
||||||
else:
|
else:
|
||||||
confirmation_header = f"{tr('Password')}: {password.hidden()}\n"
|
confirmation_header = f'{tr("Password")}: {password.hidden()}\n'
|
||||||
|
|
||||||
result = EditMenu(
|
result = EditMenu(
|
||||||
tr("Confirm password"),
|
tr('Confirm password'),
|
||||||
header=confirmation_header,
|
header=confirmation_header,
|
||||||
alignment=Alignment.CENTER,
|
alignment=Alignment.CENTER,
|
||||||
allow_skip=False,
|
allow_skip=False,
|
||||||
|
|
@ -57,7 +57,7 @@ def get_password(
|
||||||
if password._plaintext == result.text():
|
if password._plaintext == result.text():
|
||||||
return password
|
return password
|
||||||
|
|
||||||
failure = tr("The confirmation password did not match, please try again")
|
failure = tr('The confirmation password did not match, please try again')
|
||||||
|
|
||||||
|
|
||||||
def prompt_dir(
|
def prompt_dir(
|
||||||
|
|
@ -73,7 +73,7 @@ def prompt_dir(
|
||||||
if dest_path.exists() and dest_path.is_dir():
|
if dest_path.exists() and dest_path.is_dir():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return tr("Not a valid directory")
|
return tr('Not a valid directory')
|
||||||
|
|
||||||
if validate:
|
if validate:
|
||||||
validate_func = validate_path
|
validate_func = validate_path
|
||||||
|
|
@ -108,9 +108,9 @@ def is_subpath(first: Path, second: Path) -> bool:
|
||||||
|
|
||||||
def format_cols(items: list[str], header: str | None = None) -> str:
|
def format_cols(items: list[str], header: str | None = None) -> str:
|
||||||
if header:
|
if header:
|
||||||
text = f"{header}:\n"
|
text = f'{header}:\n'
|
||||||
else:
|
else:
|
||||||
text = ""
|
text = ''
|
||||||
|
|
||||||
nr_items = len(items)
|
nr_items = len(items)
|
||||||
if nr_items <= 4:
|
if nr_items <= 4:
|
||||||
|
|
@ -124,5 +124,5 @@ def format_cols(items: list[str], header: str | None = None) -> str:
|
||||||
|
|
||||||
text += FormattedOutput.as_columns(items, col)
|
text += FormattedOutput.as_columns(items, col)
|
||||||
# remove whitespaces on each row
|
# remove whitespaces on each row
|
||||||
text = "\n".join([t.strip() for t in text.split("\n")])
|
text = '\n'.join([t.strip() for t in text.split('\n')])
|
||||||
return text
|
return text
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ def ask_user_questions() -> None:
|
||||||
global_menu = GlobalMenu(arch_config_handler.config)
|
global_menu = GlobalMenu(arch_config_handler.config)
|
||||||
|
|
||||||
if not arch_config_handler.args.advanced:
|
if not arch_config_handler.args.advanced:
|
||||||
global_menu.set_enabled("parallel_downloads", False)
|
global_menu.set_enabled('parallel_downloads', False)
|
||||||
|
|
||||||
global_menu.run()
|
global_menu.run()
|
||||||
|
|
||||||
|
|
@ -42,12 +42,12 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
Only requirement is that the block devices are
|
Only requirement is that the block devices are
|
||||||
formatted and setup prior to entering this function.
|
formatted and setup prior to entering this function.
|
||||||
"""
|
"""
|
||||||
info("Starting installation...")
|
info('Starting installation...')
|
||||||
|
|
||||||
config = arch_config_handler.config
|
config = arch_config_handler.config
|
||||||
|
|
||||||
if not config.disk_config:
|
if not config.disk_config:
|
||||||
error("No disk configuration provided")
|
error('No disk configuration provided')
|
||||||
return
|
return
|
||||||
|
|
||||||
disk_config = config.disk_config
|
disk_config = config.disk_config
|
||||||
|
|
@ -88,10 +88,10 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
installation.set_mirrors(mirror_config, on_target=True)
|
installation.set_mirrors(mirror_config, on_target=True)
|
||||||
|
|
||||||
if config.swap:
|
if config.swap:
|
||||||
installation.setup_swap("zram")
|
installation.setup_swap('zram')
|
||||||
|
|
||||||
if config.bootloader == Bootloader.Grub and SysInfo.has_uefi():
|
if config.bootloader == Bootloader.Grub and SysInfo.has_uefi():
|
||||||
installation.add_additional_packages("grub")
|
installation.add_additional_packages('grub')
|
||||||
|
|
||||||
installation.add_bootloader(config.bootloader, config.uki)
|
installation.add_bootloader(config.bootloader, config.uki)
|
||||||
|
|
||||||
|
|
@ -112,9 +112,9 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
if audio_config:
|
if audio_config:
|
||||||
audio_config.install_audio_config(installation)
|
audio_config.install_audio_config(installation)
|
||||||
else:
|
else:
|
||||||
info("No audio server will be installed")
|
info('No audio server will be installed')
|
||||||
|
|
||||||
if config.packages and config.packages[0] != "":
|
if config.packages and config.packages[0] != '':
|
||||||
installation.add_additional_packages(config.packages)
|
installation.add_additional_packages(config.packages)
|
||||||
|
|
||||||
if profile_config := config.profile_config:
|
if profile_config := config.profile_config:
|
||||||
|
|
@ -130,7 +130,7 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
installation.enable_espeakup()
|
installation.enable_espeakup()
|
||||||
|
|
||||||
if root_pw := config.root_enc_password:
|
if root_pw := config.root_enc_password:
|
||||||
root_user = User("root", root_pw, False)
|
root_user = User('root', root_pw, False)
|
||||||
installation.set_user_password(root_user)
|
installation.set_user_password(root_user)
|
||||||
|
|
||||||
if (profile_config := config.profile_config) and profile_config.profile:
|
if (profile_config := config.profile_config) and profile_config.profile:
|
||||||
|
|
@ -147,7 +147,7 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
|
|
||||||
installation.genfstab()
|
installation.genfstab()
|
||||||
|
|
||||||
debug(f"Disk states after installing:\n{disk_layouts()}")
|
debug(f'Disk states after installing:\n{disk_layouts()}')
|
||||||
|
|
||||||
if not arch_config_handler.args.silent:
|
if not arch_config_handler.args.silent:
|
||||||
with Tui():
|
with Tui():
|
||||||
|
|
@ -157,7 +157,7 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
case PostInstallationAction.EXIT:
|
case PostInstallationAction.EXIT:
|
||||||
pass
|
pass
|
||||||
case PostInstallationAction.REBOOT:
|
case PostInstallationAction.REBOOT:
|
||||||
os.system("reboot")
|
os.system('reboot')
|
||||||
case PostInstallationAction.CHROOT:
|
case PostInstallationAction.CHROOT:
|
||||||
try:
|
try:
|
||||||
installation.drop_to_shell()
|
installation.drop_to_shell()
|
||||||
|
|
@ -179,7 +179,7 @@ def guided() -> None:
|
||||||
if not arch_config_handler.args.silent:
|
if not arch_config_handler.args.silent:
|
||||||
with Tui():
|
with Tui():
|
||||||
if not config.confirm_config():
|
if not config.confirm_config():
|
||||||
debug("Installation aborted")
|
debug('Installation aborted')
|
||||||
guided()
|
guided()
|
||||||
|
|
||||||
if arch_config_handler.config.disk_config:
|
if arch_config_handler.config.disk_config:
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import glob
|
import glob
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
print("The following are viable --script options:")
|
print('The following are viable --script options:')
|
||||||
|
|
||||||
for script in [Path(x) for x in glob.glob(f"{Path(__file__).parent}/*.py")]:
|
for script in [Path(x) for x in glob.glob(f'{Path(__file__).parent}/*.py')]:
|
||||||
if script.stem in ["__init__", "list"]:
|
if script.stem in ['__init__', 'list']:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print(f" {script.stem}")
|
print(f' {script.stem}')
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
config = arch_config_handler.config
|
config = arch_config_handler.config
|
||||||
|
|
||||||
if not config.disk_config:
|
if not config.disk_config:
|
||||||
error("No disk configuration provided")
|
error('No disk configuration provided')
|
||||||
return
|
return
|
||||||
|
|
||||||
disk_config = config.disk_config
|
disk_config = config.disk_config
|
||||||
|
|
@ -35,7 +35,7 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
# Strap in the base system, add a boot loader and configure
|
# Strap in the base system, add a boot loader and configure
|
||||||
# some other minor details as specified by this profile and user.
|
# some other minor details as specified by this profile and user.
|
||||||
if installation.minimal_installation():
|
if installation.minimal_installation():
|
||||||
installation.set_hostname("minimal-arch")
|
installation.set_hostname('minimal-arch')
|
||||||
installation.add_bootloader(Bootloader.Systemd)
|
installation.add_bootloader(Bootloader.Systemd)
|
||||||
|
|
||||||
network_config = config.network_config
|
network_config = config.network_config
|
||||||
|
|
@ -46,19 +46,19 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
config.profile_config,
|
config.profile_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
installation.add_additional_packages(["nano", "wget", "git"])
|
installation.add_additional_packages(['nano', 'wget', 'git'])
|
||||||
|
|
||||||
profile_config = ProfileConfiguration(MinimalProfile())
|
profile_config = ProfileConfiguration(MinimalProfile())
|
||||||
profile_handler.install_profile_config(installation, profile_config)
|
profile_handler.install_profile_config(installation, profile_config)
|
||||||
|
|
||||||
user = User("devel", Password(plaintext="devel"), False)
|
user = User('devel', Password(plaintext='devel'), False)
|
||||||
installation.create_users(user)
|
installation.create_users(user)
|
||||||
|
|
||||||
# Once this is done, we output some useful information to the user
|
# Once this is done, we output some useful information to the user
|
||||||
# And the installation is complete.
|
# And the installation is complete.
|
||||||
info("There are two new accounts in your installation after reboot:")
|
info('There are two new accounts in your installation after reboot:')
|
||||||
info(" * root (password: airoot)")
|
info(' * root (password: airoot)')
|
||||||
info(" * devel (password: devel)")
|
info(' * devel (password: devel)')
|
||||||
|
|
||||||
|
|
||||||
def _minimal() -> None:
|
def _minimal() -> None:
|
||||||
|
|
@ -82,7 +82,7 @@ def _minimal() -> None:
|
||||||
if not arch_config_handler.args.silent:
|
if not arch_config_handler.args.silent:
|
||||||
with Tui():
|
with Tui():
|
||||||
if not config.confirm_config():
|
if not config.confirm_config():
|
||||||
debug("Installation aborted")
|
debug('Installation aborted')
|
||||||
_minimal()
|
_minimal()
|
||||||
|
|
||||||
if arch_config_handler.config.disk_config:
|
if arch_config_handler.config.disk_config:
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ def ask_user_questions() -> None:
|
||||||
global_menu = GlobalMenu(arch_config_handler.config)
|
global_menu = GlobalMenu(arch_config_handler.config)
|
||||||
global_menu.disable_all()
|
global_menu.disable_all()
|
||||||
|
|
||||||
global_menu.set_enabled("archinstall_language", True)
|
global_menu.set_enabled('archinstall_language', True)
|
||||||
global_menu.set_enabled("disk_config", True)
|
global_menu.set_enabled('disk_config', True)
|
||||||
global_menu.set_enabled("disk_encryption", True)
|
global_menu.set_enabled('disk_encryption', True)
|
||||||
global_menu.set_enabled("swap", True)
|
global_menu.set_enabled('swap', True)
|
||||||
global_menu.set_enabled("__config__", True)
|
global_menu.set_enabled('__config__', True)
|
||||||
|
|
||||||
global_menu.run()
|
global_menu.run()
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
config = arch_config_handler.config
|
config = arch_config_handler.config
|
||||||
|
|
||||||
if not config.disk_config:
|
if not config.disk_config:
|
||||||
error("No disk configuration provided")
|
error('No disk configuration provided')
|
||||||
return
|
return
|
||||||
|
|
||||||
disk_config = config.disk_config
|
disk_config = config.disk_config
|
||||||
|
|
@ -52,12 +52,12 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
installation.mount_ordered_layout()
|
installation.mount_ordered_layout()
|
||||||
|
|
||||||
# to generate a fstab directory holder. Avoids an error on exit and at the same time checks the procedure
|
# to generate a fstab directory holder. Avoids an error on exit and at the same time checks the procedure
|
||||||
target = Path(f"{mountpoint}/etc/fstab")
|
target = Path(f'{mountpoint}/etc/fstab')
|
||||||
if not target.parent.exists():
|
if not target.parent.exists():
|
||||||
target.parent.mkdir(parents=True)
|
target.parent.mkdir(parents=True)
|
||||||
|
|
||||||
# For support reasons, we'll log the disk layout post installation (crash or no crash)
|
# For support reasons, we'll log the disk layout post installation (crash or no crash)
|
||||||
debug(f"Disk states after installing:\n{disk_layouts()}")
|
debug(f'Disk states after installing:\n{disk_layouts()}')
|
||||||
|
|
||||||
|
|
||||||
def _only_hd() -> None:
|
def _only_hd() -> None:
|
||||||
|
|
@ -74,7 +74,7 @@ def _only_hd() -> None:
|
||||||
if not arch_config_handler.args.silent:
|
if not arch_config_handler.args.silent:
|
||||||
with Tui():
|
with Tui():
|
||||||
if not config.confirm_config():
|
if not config.confirm_config():
|
||||||
debug("Installation aborted")
|
debug('Installation aborted')
|
||||||
_only_hd()
|
_only_hd()
|
||||||
|
|
||||||
if arch_config_handler.config.disk_config:
|
if arch_config_handler.config.disk_config:
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ for p in profile_handler.get_mac_addr_profiles():
|
||||||
# that fits the requirements for this machine specifically).
|
# that fits the requirements for this machine specifically).
|
||||||
info(f'Found a tailored profile for this machine called: "{p.name}"')
|
info(f'Found a tailored profile for this machine called: "{p.name}"')
|
||||||
|
|
||||||
print("Starting install in:")
|
print('Starting install in:')
|
||||||
for i in range(10, 0, -1):
|
for i in range(10, 0, -1):
|
||||||
Tui.print(f"{i}...")
|
Tui.print(f'{i}...')
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
install_session = storage["installation_session"]
|
install_session = storage['installation_session']
|
||||||
p.install(install_session)
|
p.install(install_session)
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue