parent
f71e91c85e
commit
c67ac97f18
2
.flake8
2
.flake8
|
|
@ -1,6 +1,6 @@
|
|||
[flake8]
|
||||
count = True
|
||||
ignore = W191,W503
|
||||
ignore = W191,W503,E704,E203
|
||||
max-complexity = 40
|
||||
max-line-length = 160
|
||||
show-source = True
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
on: [ push, pull_request ]
|
||||
name: ruff check formatting
|
||||
jobs:
|
||||
ruff_format_check:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- uses: astral-sh/ruff-action@84f83ecf9e1e15d26b7984c7ec9cf73d39ffc946 # v3.3.1
|
||||
- run: ruff check --select=COM812 --fix
|
||||
- run: ruff format --diff
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
on: [ push, pull_request ]
|
||||
name: ruff linting
|
||||
name: ruff check linting
|
||||
jobs:
|
||||
ruff:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -1,17 +1,15 @@
|
|||
default_stages: ['pre-commit']
|
||||
repos:
|
||||
- repo: https://github.com/pycqa/autoflake
|
||||
rev: v2.3.1
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.9
|
||||
hooks:
|
||||
- id: autoflake
|
||||
args: [
|
||||
'--in-place',
|
||||
'--remove-all-unused-imports',
|
||||
'--ignore-init-module-imports'
|
||||
]
|
||||
files: \.py$
|
||||
require_serial: true
|
||||
fail_fast: true
|
||||
# fix unused imports and sort them
|
||||
- id: ruff
|
||||
args: ["--extend-select", "I", "--fix"]
|
||||
# format the code
|
||||
- id: ruff-format
|
||||
# run the linter
|
||||
- id: ruff
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
|
|
@ -23,8 +21,6 @@ repos:
|
|||
- id: check-yaml # Attempts to load all yaml files to verify syntax
|
||||
- id: destroyed-symlinks # Detects symlinks which are changed to regular files
|
||||
- id: detect-private-key # Checks for the existence of private keys
|
||||
- id: end-of-file-fixer # Makes sure files end in a newline and only a newline
|
||||
- id: trailing-whitespace # Trims trailing whitespace
|
||||
# Python specific hooks:
|
||||
- id: check-ast # Simply check whether files parse as valid python
|
||||
- id: check-docstring-first # Checks for a common error of placing code before the docstring
|
||||
|
|
@ -47,13 +43,6 @@ repos:
|
|||
- pydantic
|
||||
- pytest
|
||||
- cryptography
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.9
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["check", "--select", "I", "--fix"]
|
||||
fail_fast: true
|
||||
- id: ruff
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pylint
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ def _fetch_arch_db() -> None:
|
|||
try:
|
||||
Pacman.run("-Sy")
|
||||
except Exception as e:
|
||||
debug(f'Failed to sync Arch Linux package database: {e}')
|
||||
debug(f"Failed to sync Arch Linux package database: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
|
|
@ -57,10 +57,10 @@ def _check_new_version() -> None:
|
|||
try:
|
||||
upgrade = Pacman.run("-Qu archinstall").decode()
|
||||
except Exception as e:
|
||||
debug(f'Failed determine pacman version: {e}')
|
||||
debug(f"Failed determine pacman version: {e}")
|
||||
|
||||
if upgrade:
|
||||
text = f'New version available: {upgrade}'
|
||||
text = f"New version available: {upgrade}"
|
||||
info(text)
|
||||
time.sleep(3)
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ def main() -> int:
|
|||
OR straight as a module: python -m archinstall
|
||||
In any case we will be attempting to load the provided script to be run from the scripts/ folder
|
||||
"""
|
||||
if '--help' in sys.argv or '-h' in sys.argv:
|
||||
if "--help" in sys.argv or "-h" in sys.argv:
|
||||
arch_config_handler.print_help()
|
||||
return 0
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ def main() -> int:
|
|||
|
||||
script = arch_config_handler.args.script
|
||||
|
||||
mod_name = f'archinstall.scripts.{script}'
|
||||
mod_name = f"archinstall.scripts.{script}"
|
||||
# by loading the module we'll automatically run the script
|
||||
importlib.import_module(mod_name)
|
||||
|
||||
|
|
@ -109,13 +109,13 @@ def run_as_a_module() -> None:
|
|||
Tui.shutdown()
|
||||
|
||||
if exc:
|
||||
err = ''.join(traceback.format_exception(exc))
|
||||
err = "".join(traceback.format_exception(exc))
|
||||
error(err)
|
||||
|
||||
text = (
|
||||
'Archinstall experienced the above error. If you think this is a bug, please report it to\n'
|
||||
"Archinstall experienced the above error. If you think this is a bug, please report it to\n"
|
||||
'https://github.com/archlinux/archinstall and include the log file "/var/log/archinstall/install.log".\n\n'
|
||||
'Hint: To extract the log from a live ISO \ncurl -F\'file=@/var/log/archinstall/install.log\' https://0x0.st\n'
|
||||
"Hint: To extract the log from a live ISO \ncurl -F'file=@/var/log/archinstall/install.log' https://0x0.st\n"
|
||||
)
|
||||
|
||||
warn(text)
|
||||
|
|
@ -125,20 +125,20 @@ def run_as_a_module() -> None:
|
|||
|
||||
|
||||
__all__ = [
|
||||
'DeferredTranslation',
|
||||
'FormattedOutput',
|
||||
'Language',
|
||||
'Pacman',
|
||||
'SysInfo',
|
||||
'Tui',
|
||||
'arch_config_handler',
|
||||
'debug',
|
||||
'disk_layouts',
|
||||
'error',
|
||||
'info',
|
||||
'load_plugin',
|
||||
'log',
|
||||
'plugin',
|
||||
'translation_handler',
|
||||
'warn',
|
||||
"DeferredTranslation",
|
||||
"FormattedOutput",
|
||||
"Language",
|
||||
"Pacman",
|
||||
"SysInfo",
|
||||
"Tui",
|
||||
"arch_config_handler",
|
||||
"debug",
|
||||
"disk_layouts",
|
||||
"error",
|
||||
"info",
|
||||
"load_plugin",
|
||||
"log",
|
||||
"plugin",
|
||||
"translation_handler",
|
||||
"warn",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import archinstall
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
archinstall.run_as_a_module()
|
||||
|
|
|
|||
|
|
@ -9,23 +9,24 @@ if TYPE_CHECKING:
|
|||
|
||||
class PipewireProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Pipewire', ProfileType.Application)
|
||||
super().__init__("Pipewire", ProfileType.Application)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'pipewire',
|
||||
'pipewire-alsa',
|
||||
'pipewire-jack',
|
||||
'pipewire-pulse',
|
||||
'gst-plugin-pipewire',
|
||||
'libpulse',
|
||||
'wireplumber'
|
||||
"pipewire",
|
||||
"pipewire-alsa",
|
||||
"pipewire-jack",
|
||||
"pipewire-pulse",
|
||||
"gst-plugin-pipewire",
|
||||
"libpulse",
|
||||
"wireplumber",
|
||||
]
|
||||
|
||||
def _enable_pipewire_for_all(self, install_session: 'Installer') -> None:
|
||||
def _enable_pipewire_for_all(self, install_session: "Installer") -> None:
|
||||
from archinstall.lib.args import arch_config_handler
|
||||
|
||||
users: list[User] | None = arch_config_handler.config.users
|
||||
|
||||
if users is None:
|
||||
|
|
@ -37,20 +38,20 @@ class PipewireProfile(Profile):
|
|||
service_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Set ownership of the entire user catalogue
|
||||
install_session.arch_chroot(f'chown -R {user.username}:{user.username} /home/{user.username}')
|
||||
install_session.arch_chroot(f"chown -R {user.username}:{user.username} /home/{user.username}")
|
||||
|
||||
# symlink in the correct pipewire systemd items
|
||||
install_session.arch_chroot(
|
||||
f'ln -sf /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service',
|
||||
run_as=user.username
|
||||
f"ln -sf /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service",
|
||||
run_as=user.username,
|
||||
)
|
||||
install_session.arch_chroot(
|
||||
f'ln -sf /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket',
|
||||
run_as=user.username
|
||||
f"ln -sf /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket",
|
||||
run_as=user.username,
|
||||
)
|
||||
|
||||
@override
|
||||
def install(self, install_session: 'Installer') -> None:
|
||||
def install(self, install_session: "Installer") -> None:
|
||||
super().install(install_session)
|
||||
install_session.add_additional_packages(self.packages)
|
||||
self._enable_pipewire_for_all(install_session)
|
||||
|
|
|
|||
|
|
@ -15,26 +15,26 @@ if TYPE_CHECKING:
|
|||
class DesktopProfile(Profile):
|
||||
def __init__(self, current_selection: list[Profile] = []) -> None:
|
||||
super().__init__(
|
||||
'Desktop',
|
||||
"Desktop",
|
||||
ProfileType.Desktop,
|
||||
current_selection=current_selection,
|
||||
support_greeter=True
|
||||
support_greeter=True,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'nano',
|
||||
'vim',
|
||||
'openssh',
|
||||
'htop',
|
||||
'wget',
|
||||
'iwd',
|
||||
'wireless_tools',
|
||||
'wpa_supplicant',
|
||||
'smartmontools',
|
||||
'xdg-utils'
|
||||
"nano",
|
||||
"vim",
|
||||
"openssh",
|
||||
"htop",
|
||||
"wget",
|
||||
"iwd",
|
||||
"wireless_tools",
|
||||
"wpa_supplicant",
|
||||
"smartmontools",
|
||||
"xdg-utils",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
@ -61,8 +61,9 @@ class DesktopProfile(Profile):
|
|||
MenuItem(
|
||||
p.name,
|
||||
value=p,
|
||||
preview_action=lambda x: x.value.preview_text()
|
||||
) for p in profile_handler.get_desktop_profiles()
|
||||
preview_action=lambda x: x.value.preview_text(),
|
||||
)
|
||||
for p in profile_handler.get_desktop_profiles()
|
||||
]
|
||||
|
||||
group = MenuItemGroup(items, sort_items=True, sort_case_sensitive=False)
|
||||
|
|
@ -74,8 +75,8 @@ class DesktopProfile(Profile):
|
|||
allow_reset=True,
|
||||
allow_skip=True,
|
||||
preview_style=PreviewStyle.RIGHT,
|
||||
preview_size='auto',
|
||||
preview_frame=FrameProperties.max('Info')
|
||||
preview_size="auto",
|
||||
preview_frame=FrameProperties.max("Info"),
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -89,17 +90,17 @@ class DesktopProfile(Profile):
|
|||
return SelectResult.ResetCurrent
|
||||
|
||||
@override
|
||||
def post_install(self, install_session: 'Installer') -> None:
|
||||
def post_install(self, install_session: "Installer") -> None:
|
||||
for profile in self.current_selection:
|
||||
profile.post_install(install_session)
|
||||
|
||||
@override
|
||||
def install(self, install_session: 'Installer') -> None:
|
||||
def install(self, install_session: "Installer") -> None:
|
||||
# Install common packages for all desktop environments
|
||||
install_session.add_additional_packages(self.packages)
|
||||
|
||||
for profile in self.current_selection:
|
||||
info(f'Installing profile {profile.name}...')
|
||||
info(f"Installing profile {profile.name}...")
|
||||
|
||||
install_session.add_additional_packages(profile.packages)
|
||||
install_session.enable_service(profile.services)
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ from enum import Enum
|
|||
|
||||
|
||||
class SeatAccess(Enum):
|
||||
seatd = 'seatd'
|
||||
polkit = 'polkit'
|
||||
seatd = "seatd"
|
||||
polkit = "polkit"
|
||||
|
|
|
|||
|
|
@ -9,27 +9,27 @@ if TYPE_CHECKING:
|
|||
|
||||
class AwesomeProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Awesome', ProfileType.WindowMgr)
|
||||
super().__init__("Awesome", ProfileType.WindowMgr)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return super().packages + [
|
||||
'awesome',
|
||||
'alacritty',
|
||||
'xorg-xinit',
|
||||
'xorg-xrandr',
|
||||
'xterm',
|
||||
'feh',
|
||||
'slock',
|
||||
'terminus-font',
|
||||
'gnu-free-fonts',
|
||||
'ttf-liberation',
|
||||
'xsel',
|
||||
"awesome",
|
||||
"alacritty",
|
||||
"xorg-xinit",
|
||||
"xorg-xrandr",
|
||||
"xterm",
|
||||
"feh",
|
||||
"slock",
|
||||
"terminus-font",
|
||||
"gnu-free-fonts",
|
||||
"ttf-liberation",
|
||||
"xsel",
|
||||
]
|
||||
|
||||
@override
|
||||
def install(self, install_session: 'Installer') -> None:
|
||||
def install(self, install_session: "Installer") -> None:
|
||||
super().install(install_session)
|
||||
|
||||
# TODO: Copy a full configuration to ~/.config/awesome/rc.lua instead.
|
||||
|
|
@ -39,7 +39,7 @@ class AwesomeProfile(XorgProfile):
|
|||
# Replace xterm with alacritty for a smoother experience.
|
||||
awesome_lua = awesome_lua.replace('"xterm"', '"alacritty"')
|
||||
|
||||
with open(f"{install_session.target}/etc/xdg/awesome/rc.lua", 'w') as fh:
|
||||
with open(f"{install_session.target}/etc/xdg/awesome/rc.lua", "w") as fh:
|
||||
fh.write(awesome_lua)
|
||||
|
||||
# TODO: Configure the right-click-menu to contain the above packages that were installed. (as a user config)
|
||||
|
|
@ -49,7 +49,7 @@ class AwesomeProfile(XorgProfile):
|
|||
with open(f"{install_session.target}/etc/X11/xinit/xinitrc") as xinitrc:
|
||||
xinitrc_data = xinitrc.read()
|
||||
|
||||
for line in xinitrc_data.split('\n'):
|
||||
for line in xinitrc_data.split("\n"):
|
||||
if "twm &" in line:
|
||||
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
|
||||
if "xclock" in line:
|
||||
|
|
@ -57,8 +57,8 @@ class AwesomeProfile(XorgProfile):
|
|||
if "xterm" in line:
|
||||
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
|
||||
|
||||
xinitrc_data += '\n'
|
||||
xinitrc_data += 'exec awesome\n'
|
||||
xinitrc_data += "\n"
|
||||
xinitrc_data += "exec awesome\n"
|
||||
|
||||
with open(f"{install_session.target}/etc/X11/xinit/xinitrc", 'w') as xinitrc:
|
||||
with open(f"{install_session.target}/etc/X11/xinit/xinitrc", "w") as xinitrc:
|
||||
xinitrc.write(xinitrc_data)
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class BspwmProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Bspwm', ProfileType.WindowMgr)
|
||||
super().__init__("Bspwm", ProfileType.WindowMgr)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
# return super().packages + [
|
||||
return [
|
||||
'bspwm',
|
||||
'sxhkd',
|
||||
'dmenu',
|
||||
'xdo',
|
||||
'rxvt-unicode'
|
||||
"bspwm",
|
||||
"sxhkd",
|
||||
"dmenu",
|
||||
"xdo",
|
||||
"rxvt-unicode",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class BudgieProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Budgie', ProfileType.DesktopEnv)
|
||||
super().__init__("Budgie", ProfileType.DesktopEnv)
|
||||
|
||||
@property
|
||||
@override
|
||||
|
|
@ -16,7 +16,7 @@ class BudgieProfile(XorgProfile):
|
|||
"budgie",
|
||||
"mate-terminal",
|
||||
"nemo",
|
||||
"papirus-icon-theme"
|
||||
"papirus-icon-theme",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class CinnamonProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Cinnamon', ProfileType.DesktopEnv)
|
||||
super().__init__("Cinnamon", ProfileType.DesktopEnv)
|
||||
|
||||
@property
|
||||
@override
|
||||
|
|
@ -22,7 +22,7 @@ class CinnamonProfile(XorgProfile):
|
|||
"gnome-screenshot",
|
||||
"gvfs-smb",
|
||||
"xed",
|
||||
"xdg-user-dirs-gtk"
|
||||
"xdg-user-dirs-gtk",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class CosmicProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('cosmic-epoch', ProfileType.DesktopEnv, advanced=True)
|
||||
super().__init__("cosmic-epoch", ProfileType.DesktopEnv, advanced=True)
|
||||
|
||||
@property
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class CutefishProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Cutefish', ProfileType.DesktopEnv)
|
||||
super().__init__("Cutefish", ProfileType.DesktopEnv)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
"cutefish",
|
||||
"noto-fonts"
|
||||
"noto-fonts",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class DeepinProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Deepin', ProfileType.DesktopEnv)
|
||||
super().__init__("Deepin", ProfileType.DesktopEnv)
|
||||
|
||||
@property
|
||||
@override
|
||||
|
|
@ -14,7 +14,7 @@ class DeepinProfile(XorgProfile):
|
|||
return [
|
||||
"deepin",
|
||||
"deepin-terminal",
|
||||
"deepin-editor"
|
||||
"deepin-editor",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class EnlighenmentProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Enlightenment', ProfileType.WindowMgr)
|
||||
super().__init__("Enlightenment", ProfileType.WindowMgr)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
"enlightenment",
|
||||
"terminology"
|
||||
"terminology",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class GnomeProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('GNOME', ProfileType.DesktopEnv)
|
||||
super().__init__("GNOME", ProfileType.DesktopEnv)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'gnome',
|
||||
'gnome-tweaks'
|
||||
"gnome",
|
||||
"gnome-tweaks",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ if TYPE_CHECKING:
|
|||
|
||||
class HyprlandProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Hyprland', ProfileType.DesktopEnv)
|
||||
super().__init__("Hyprland", ProfileType.DesktopEnv)
|
||||
|
||||
self.custom_settings = {'seat_access': None}
|
||||
self.custom_settings = {"seat_access": None}
|
||||
|
||||
@property
|
||||
@override
|
||||
|
|
@ -48,32 +48,32 @@ class HyprlandProfile(XorgProfile):
|
|||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get('seat_access', None):
|
||||
if pref := self.custom_settings.get("seat_access", None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
def _ask_seat_access(self) -> None:
|
||||
# need to activate seat service and add to seat group
|
||||
header = str(_('Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)'))
|
||||
header += '\n' + str(_('Choose an option to give Hyprland access to your hardware')) + '\n'
|
||||
header = str(_("Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"))
|
||||
header += "\n" + str(_("Choose an option to give Hyprland access to your hardware")) + "\n"
|
||||
|
||||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
||||
default = self.custom_settings.get('seat_access', None)
|
||||
default = self.custom_settings.get("seat_access", None)
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = SelectMenu[SeatAccess](
|
||||
group,
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
frame=FrameProperties.min(str(_('Seat access'))),
|
||||
alignment=Alignment.CENTER
|
||||
frame=FrameProperties.min(str(_("Seat access"))),
|
||||
alignment=Alignment.CENTER,
|
||||
).run()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
if result.item() is not None:
|
||||
self.custom_settings['seat_access'] = result.get_value().value
|
||||
self.custom_settings["seat_access"] = result.get_value().value
|
||||
|
||||
@override
|
||||
def do_on_select(self) -> None:
|
||||
|
|
|
|||
|
|
@ -6,21 +6,21 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class I3wmProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('i3-wm', ProfileType.WindowMgr)
|
||||
super().__init__("i3-wm", ProfileType.WindowMgr)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'i3-wm',
|
||||
'i3lock',
|
||||
'i3status',
|
||||
'i3blocks',
|
||||
'xss-lock',
|
||||
'xterm',
|
||||
'lightdm-gtk-greeter',
|
||||
'lightdm',
|
||||
'dmenu'
|
||||
"i3-wm",
|
||||
"i3lock",
|
||||
"i3status",
|
||||
"i3blocks",
|
||||
"xss-lock",
|
||||
"xterm",
|
||||
"lightdm-gtk-greeter",
|
||||
"lightdm",
|
||||
"dmenu",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -19,17 +19,17 @@ if TYPE_CHECKING:
|
|||
class LabwcProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Labwc',
|
||||
"Labwc",
|
||||
ProfileType.WindowMgr,
|
||||
)
|
||||
|
||||
self.custom_settings = {'seat_access': None}
|
||||
self.custom_settings = {"seat_access": None}
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
additional = []
|
||||
if seat := self.custom_settings.get('seat_access', None):
|
||||
if seat := self.custom_settings.get("seat_access", None):
|
||||
additional = [seat]
|
||||
|
||||
return [
|
||||
|
|
@ -45,32 +45,32 @@ class LabwcProfile(XorgProfile):
|
|||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get('seat_access', None):
|
||||
if pref := self.custom_settings.get("seat_access", None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
def _ask_seat_access(self) -> None:
|
||||
# need to activate seat service and add to seat group
|
||||
header = str(_('labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)'))
|
||||
header += '\n' + str(_('Choose an option to give labwc access to your hardware')) + '\n'
|
||||
header = str(_("labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"))
|
||||
header += "\n" + str(_("Choose an option to give labwc access to your hardware")) + "\n"
|
||||
|
||||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
||||
default = self.custom_settings.get('seat_access', None)
|
||||
default = self.custom_settings.get("seat_access", None)
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = SelectMenu[SeatAccess](
|
||||
group,
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
frame=FrameProperties.min(str(_('Seat access'))),
|
||||
alignment=Alignment.CENTER
|
||||
frame=FrameProperties.min(str(_("Seat access"))),
|
||||
alignment=Alignment.CENTER,
|
||||
).run()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
if result.item() is not None:
|
||||
self.custom_settings['seat_access'] = result.get_value().value
|
||||
self.custom_settings["seat_access"] = result.get_value().value
|
||||
|
||||
@override
|
||||
def do_on_select(self) -> None:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class LxqtProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Lxqt', ProfileType.DesktopEnv)
|
||||
super().__init__("Lxqt", ProfileType.DesktopEnv)
|
||||
|
||||
# NOTE: SDDM is the only officially supported greeter for LXQt, so unlike other DEs, lightdm is not used here.
|
||||
# LXQt works with lightdm, but since this is not supported, we will not default to this.
|
||||
|
|
@ -21,7 +21,7 @@ class LxqtProfile(XorgProfile):
|
|||
"xdg-utils",
|
||||
"ttf-freefont",
|
||||
"leafpad",
|
||||
"slock"
|
||||
"slock",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class MateProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Mate', ProfileType.DesktopEnv)
|
||||
super().__init__("Mate", ProfileType.DesktopEnv)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
"mate",
|
||||
"mate-extra"
|
||||
"mate-extra",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -19,17 +19,17 @@ if TYPE_CHECKING:
|
|||
class NiriProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Niri',
|
||||
"Niri",
|
||||
ProfileType.WindowMgr,
|
||||
)
|
||||
|
||||
self.custom_settings = {'seat_access': None}
|
||||
self.custom_settings = {"seat_access": None}
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
additional = []
|
||||
if seat := self.custom_settings.get('seat_access', None):
|
||||
if seat := self.custom_settings.get("seat_access", None):
|
||||
additional = [seat]
|
||||
|
||||
return [
|
||||
|
|
@ -42,7 +42,7 @@ class NiriProfile(XorgProfile):
|
|||
"swaybg",
|
||||
"swayidle",
|
||||
"swaylock",
|
||||
"xdg-desktop-portal-gnome"
|
||||
"xdg-desktop-portal-gnome",
|
||||
] + additional
|
||||
|
||||
@property
|
||||
|
|
@ -53,32 +53,32 @@ class NiriProfile(XorgProfile):
|
|||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get('seat_access', None):
|
||||
if pref := self.custom_settings.get("seat_access", None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
def _ask_seat_access(self) -> None:
|
||||
# need to activate seat service and add to seat group
|
||||
header = str(_('niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)'))
|
||||
header += '\n' + str(_('Choose an option to give niri access to your hardware')) + '\n'
|
||||
header = str(_("niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"))
|
||||
header += "\n" + str(_("Choose an option to give niri access to your hardware")) + "\n"
|
||||
|
||||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
||||
default = self.custom_settings.get('seat_access', None)
|
||||
default = self.custom_settings.get("seat_access", None)
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = SelectMenu[SeatAccess](
|
||||
group,
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
frame=FrameProperties.min(str(_('Seat access'))),
|
||||
alignment=Alignment.CENTER
|
||||
frame=FrameProperties.min(str(_("Seat access"))),
|
||||
alignment=Alignment.CENTER,
|
||||
).run()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
if result.item() is not None:
|
||||
self.custom_settings['seat_access'] = result.get_value().value
|
||||
self.custom_settings["seat_access"] = result.get_value().value
|
||||
|
||||
@override
|
||||
def do_on_select(self) -> None:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class PlasmaProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('KDE Plasma', ProfileType.DesktopEnv)
|
||||
super().__init__("KDE Plasma", ProfileType.DesktopEnv)
|
||||
|
||||
@property
|
||||
@override
|
||||
|
|
@ -17,7 +17,7 @@ class PlasmaProfile(XorgProfile):
|
|||
"kate",
|
||||
"dolphin",
|
||||
"ark",
|
||||
"plasma-workspace"
|
||||
"plasma-workspace",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class QtileProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Qtile', ProfileType.WindowMgr)
|
||||
super().__init__("Qtile", ProfileType.WindowMgr)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'qtile',
|
||||
'alacritty'
|
||||
"qtile",
|
||||
"alacritty",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class RiverProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('River', ProfileType.WindowMgr)
|
||||
super().__init__("River", ProfileType.WindowMgr)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'foot',
|
||||
'xdg-desktop-portal-wlr',
|
||||
'river'
|
||||
"foot",
|
||||
"xdg-desktop-portal-wlr",
|
||||
"river",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -19,17 +19,17 @@ if TYPE_CHECKING:
|
|||
class SwayProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Sway',
|
||||
"Sway",
|
||||
ProfileType.WindowMgr,
|
||||
)
|
||||
|
||||
self.custom_settings = {'seat_access': None}
|
||||
self.custom_settings = {"seat_access": None}
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
additional = []
|
||||
if seat := self.custom_settings.get('seat_access', None):
|
||||
if seat := self.custom_settings.get("seat_access", None):
|
||||
additional = [seat]
|
||||
|
||||
return [
|
||||
|
|
@ -44,7 +44,7 @@ class SwayProfile(XorgProfile):
|
|||
"slurp",
|
||||
"pavucontrol",
|
||||
"foot",
|
||||
"xorg-xwayland"
|
||||
"xorg-xwayland",
|
||||
] + additional
|
||||
|
||||
@property
|
||||
|
|
@ -55,32 +55,32 @@ class SwayProfile(XorgProfile):
|
|||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get('seat_access', None):
|
||||
if pref := self.custom_settings.get("seat_access", None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
def _ask_seat_access(self) -> None:
|
||||
# need to activate seat service and add to seat group
|
||||
header = str(_('Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)'))
|
||||
header += '\n' + str(_('Choose an option to give Sway access to your hardware')) + '\n'
|
||||
header = str(_("Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"))
|
||||
header += "\n" + str(_("Choose an option to give Sway access to your hardware")) + "\n"
|
||||
|
||||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
||||
default = self.custom_settings.get('seat_access', None)
|
||||
default = self.custom_settings.get("seat_access", None)
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = SelectMenu[SeatAccess](
|
||||
group,
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
frame=FrameProperties.min(str(_('Seat access'))),
|
||||
alignment=Alignment.CENTER
|
||||
frame=FrameProperties.min(str(_("Seat access"))),
|
||||
alignment=Alignment.CENTER,
|
||||
).run()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
if result.item() is not None:
|
||||
self.custom_settings['seat_access'] = result.get_value().value
|
||||
self.custom_settings["seat_access"] = result.get_value().value
|
||||
|
||||
@override
|
||||
def do_on_select(self) -> None:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class Xfce4Profile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Xfce4', ProfileType.DesktopEnv)
|
||||
super().__init__("Xfce4", ProfileType.DesktopEnv)
|
||||
|
||||
@property
|
||||
@override
|
||||
|
|
@ -16,7 +16,7 @@ class Xfce4Profile(XorgProfile):
|
|||
"xfce4-goodies",
|
||||
"pavucontrol",
|
||||
"gvfs",
|
||||
"xarchiver"
|
||||
"xarchiver",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -6,17 +6,17 @@ from archinstall.default_profiles.xorg import XorgProfile
|
|||
|
||||
class XmonadProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Xmonad', ProfileType.WindowMgr)
|
||||
super().__init__("Xmonad", ProfileType.WindowMgr)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'xmonad',
|
||||
'xmonad-contrib',
|
||||
'xmonad-extra',
|
||||
'xterm',
|
||||
'dmenu'
|
||||
"xmonad",
|
||||
"xmonad-contrib",
|
||||
"xmonad-extra",
|
||||
"xterm",
|
||||
"dmenu",
|
||||
]
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
|||
class MinimalProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Minimal',
|
||||
"Minimal",
|
||||
ProfileType.Minimal,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,30 +16,30 @@ if TYPE_CHECKING:
|
|||
|
||||
class ProfileType(Enum):
|
||||
# top level default_profiles
|
||||
Server = 'Server'
|
||||
Desktop = 'Desktop'
|
||||
Xorg = 'Xorg'
|
||||
Minimal = 'Minimal'
|
||||
Custom = 'Custom'
|
||||
Server = "Server"
|
||||
Desktop = "Desktop"
|
||||
Xorg = "Xorg"
|
||||
Minimal = "Minimal"
|
||||
Custom = "Custom"
|
||||
# detailed selection default_profiles
|
||||
ServerType = 'ServerType'
|
||||
WindowMgr = 'Window Manager'
|
||||
DesktopEnv = 'Desktop Environment'
|
||||
CustomType = 'CustomType'
|
||||
ServerType = "ServerType"
|
||||
WindowMgr = "Window Manager"
|
||||
DesktopEnv = "Desktop Environment"
|
||||
CustomType = "CustomType"
|
||||
# special things
|
||||
Tailored = 'Tailored'
|
||||
Application = 'Application'
|
||||
Tailored = "Tailored"
|
||||
Application = "Application"
|
||||
|
||||
|
||||
class GreeterType(Enum):
|
||||
Lightdm = 'lightdm-gtk-greeter'
|
||||
LightdmSlick = 'lightdm-slick-greeter'
|
||||
Sddm = 'sddm'
|
||||
Gdm = 'gdm'
|
||||
Ly = 'ly'
|
||||
Lightdm = "lightdm-gtk-greeter"
|
||||
LightdmSlick = "lightdm-slick-greeter"
|
||||
Sddm = "sddm"
|
||||
Gdm = "gdm"
|
||||
Ly = "ly"
|
||||
|
||||
# .. todo:: Remove when we un-hide cosmic behind --advanced
|
||||
if '--advanced' in sys.argv:
|
||||
if "--advanced" in sys.argv:
|
||||
CosmicSession = "cosmic-greeter"
|
||||
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ class Profile:
|
|||
services: list[str] = [],
|
||||
support_gfx_driver: bool = False,
|
||||
support_greeter: bool = False,
|
||||
advanced: bool = False
|
||||
advanced: bool = False,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.profile_type = profile_type
|
||||
|
|
@ -107,14 +107,15 @@ class Profile:
|
|||
Returns True if --advanced is given on a Profile(advanced=True) instance.
|
||||
"""
|
||||
from archinstall.lib.args import arch_config_handler
|
||||
|
||||
return self.advanced is False or arch_config_handler.args.advanced is True
|
||||
|
||||
def install(self, install_session: 'Installer') -> None:
|
||||
def install(self, install_session: "Installer") -> None:
|
||||
"""
|
||||
Performs installation steps when this profile was selected
|
||||
"""
|
||||
|
||||
def post_install(self, install_session: 'Installer') -> None:
|
||||
def post_install(self, install_session: "Installer") -> None:
|
||||
"""
|
||||
Hook that will be called when the installation process is
|
||||
finished and custom installation steps for specific default_profiles
|
||||
|
|
@ -199,9 +200,9 @@ class Profile:
|
|||
if sub_profile.packages:
|
||||
packages.update(sub_profile.packages)
|
||||
|
||||
text = str(_('Installed packages')) + ':\n'
|
||||
text = str(_("Installed packages")) + ":\n"
|
||||
|
||||
for pkg in sorted(packages):
|
||||
text += f'\t- {pkg}\n'
|
||||
text += f"\t- {pkg}\n"
|
||||
|
||||
return text
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ if TYPE_CHECKING:
|
|||
class ServerProfile(Profile):
|
||||
def __init__(self, current_value: list[Profile] = []):
|
||||
super().__init__(
|
||||
'Server',
|
||||
"Server",
|
||||
ProfileType.Server,
|
||||
current_selection=current_value
|
||||
current_selection=current_value,
|
||||
)
|
||||
|
||||
@override
|
||||
|
|
@ -26,8 +26,9 @@ class ServerProfile(Profile):
|
|||
MenuItem(
|
||||
p.name,
|
||||
value=p,
|
||||
preview_action=lambda x: x.value.preview_text()
|
||||
) for p in profile_handler.get_server_profiles()
|
||||
preview_action=lambda x: x.value.preview_text(),
|
||||
)
|
||||
for p in profile_handler.get_server_profiles()
|
||||
]
|
||||
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
|
@ -38,9 +39,9 @@ class ServerProfile(Profile):
|
|||
allow_reset=True,
|
||||
allow_skip=True,
|
||||
preview_style=PreviewStyle.RIGHT,
|
||||
preview_size='auto',
|
||||
preview_frame=FrameProperties.max('Info'),
|
||||
multi=True
|
||||
preview_size="auto",
|
||||
preview_frame=FrameProperties.max("Info"),
|
||||
multi=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -54,20 +55,20 @@ class ServerProfile(Profile):
|
|||
return SelectResult.ResetCurrent
|
||||
|
||||
@override
|
||||
def post_install(self, install_session: 'Installer') -> None:
|
||||
def post_install(self, install_session: "Installer") -> None:
|
||||
for profile in self.current_selection:
|
||||
profile.post_install(install_session)
|
||||
|
||||
@override
|
||||
def install(self, install_session: 'Installer') -> None:
|
||||
def install(self, install_session: "Installer") -> None:
|
||||
server_info = self.current_selection_names()
|
||||
details = ', '.join(server_info)
|
||||
info(f'Now installing the selected servers: {details}')
|
||||
details = ", ".join(server_info)
|
||||
info(f"Now installing the selected servers: {details}")
|
||||
|
||||
for server in self.current_selection:
|
||||
info(f'Installing {server.name}...')
|
||||
info(f"Installing {server.name}...")
|
||||
install_session.add_additional_packages(server.packages)
|
||||
install_session.enable_service(server.services)
|
||||
server.install(install_session)
|
||||
|
||||
info('If your selections included multiple servers with the same port, you may have to reconfigure them.')
|
||||
info("If your selections included multiple servers with the same port, you may have to reconfigure them.")
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
|||
class CockpitProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Cockpit',
|
||||
ProfileType.ServerType
|
||||
"Cockpit",
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['cockpit', 'udisks2', 'packagekit']
|
||||
return ["cockpit", "udisks2", "packagekit"]
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['cockpit.socket']
|
||||
return ["cockpit.socket"]
|
||||
|
|
|
|||
|
|
@ -9,22 +9,23 @@ if TYPE_CHECKING:
|
|||
class DockerProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Docker',
|
||||
ProfileType.ServerType
|
||||
"Docker",
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['docker']
|
||||
return ["docker"]
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['docker']
|
||||
return ["docker"]
|
||||
|
||||
@override
|
||||
def post_install(self, install_session: 'Installer') -> None:
|
||||
def post_install(self, install_session: "Installer") -> None:
|
||||
from archinstall.lib.args import arch_config_handler
|
||||
|
||||
for user in arch_config_handler.config.users:
|
||||
install_session.arch_chroot(f'usermod -a -G docker {user.username}')
|
||||
install_session.arch_chroot(f"usermod -a -G docker {user.username}")
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
|||
class HttpdProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'httpd',
|
||||
ProfileType.ServerType
|
||||
"httpd",
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['apache']
|
||||
return ["apache"]
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['httpd']
|
||||
return ["httpd"]
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
|||
class LighttpdProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Lighttpd',
|
||||
ProfileType.ServerType
|
||||
"Lighttpd",
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['lighttpd']
|
||||
return ["lighttpd"]
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['lighttpd']
|
||||
return ["lighttpd"]
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@ if TYPE_CHECKING:
|
|||
class MariadbProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Mariadb',
|
||||
ProfileType.ServerType
|
||||
"Mariadb",
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['mariadb']
|
||||
return ["mariadb"]
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['mariadb']
|
||||
return ["mariadb"]
|
||||
|
||||
@override
|
||||
def post_install(self, install_session: 'Installer') -> None:
|
||||
install_session.arch_chroot('mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql')
|
||||
def post_install(self, install_session: "Installer") -> None:
|
||||
install_session.arch_chroot("mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql")
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
|||
class NginxProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Nginx',
|
||||
ProfileType.ServerType
|
||||
"Nginx",
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['nginx']
|
||||
return ["nginx"]
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['nginx']
|
||||
return ["nginx"]
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@ if TYPE_CHECKING:
|
|||
class PostgresqlProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Postgresql',
|
||||
"Postgresql",
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['postgresql']
|
||||
return ["postgresql"]
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['postgresql']
|
||||
return ["postgresql"]
|
||||
|
||||
@override
|
||||
def post_install(self, install_session: 'Installer') -> None:
|
||||
install_session.arch_chroot("initdb -D /var/lib/postgres/data", run_as='postgres')
|
||||
def post_install(self, install_session: "Installer") -> None:
|
||||
install_session.arch_chroot("initdb -D /var/lib/postgres/data", run_as="postgres")
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
|||
class SshdProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'sshd',
|
||||
ProfileType.ServerType
|
||||
"sshd",
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['openssh']
|
||||
return ["openssh"]
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['sshd']
|
||||
return ["sshd"]
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ from archinstall.default_profiles.profile import Profile, ProfileType
|
|||
class TomcatProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Tomcat',
|
||||
ProfileType.ServerType
|
||||
"Tomcat",
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['tomcat10']
|
||||
return ["tomcat10"]
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['tomcat10']
|
||||
return ["tomcat10"]
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ if TYPE_CHECKING:
|
|||
|
||||
class TailoredProfile(XorgProfile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('52-54-00-12-34-56', ProfileType.Tailored)
|
||||
super().__init__("52-54-00-12-34-56", ProfileType.Tailored)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['nano', 'wget', 'git']
|
||||
return ["nano", "wget", "git"]
|
||||
|
||||
@override
|
||||
def install(self, install_session: 'Installer') -> None:
|
||||
def install(self, install_session: "Installer") -> None:
|
||||
super().install(install_session)
|
||||
# do whatever you like here :)
|
||||
|
|
|
|||
|
|
@ -13,22 +13,22 @@ if TYPE_CHECKING:
|
|||
class XorgProfile(Profile):
|
||||
def __init__(
|
||||
self,
|
||||
name: str = 'Xorg',
|
||||
name: str = "Xorg",
|
||||
profile_type: ProfileType = ProfileType.Xorg,
|
||||
advanced: bool = False
|
||||
advanced: bool = False,
|
||||
):
|
||||
super().__init__(
|
||||
name,
|
||||
profile_type,
|
||||
support_gfx_driver=True,
|
||||
advanced=advanced
|
||||
advanced=advanced,
|
||||
)
|
||||
|
||||
@override
|
||||
def preview_text(self) -> str:
|
||||
text = str(_('Environment type: {}')).format(self.profile_type.value)
|
||||
text = str(_("Environment type: {}")).format(self.profile_type.value)
|
||||
if packages := self.packages_text():
|
||||
text += f'\n{packages}'
|
||||
text += f"\n{packages}"
|
||||
|
||||
return text
|
||||
|
||||
|
|
@ -36,5 +36,5 @@ class XorgProfile(Profile):
|
|||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'xorg-server'
|
||||
"xorg-server",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ class Arguments:
|
|||
creds_decryption_key: str | None = None
|
||||
silent: bool = False
|
||||
dry_run: bool = False
|
||||
script: str = 'guided'
|
||||
mountpoint: Path = Path('/mnt')
|
||||
script: str = "guided"
|
||||
mountpoint: Path = Path("/mnt")
|
||||
skip_ntp: bool = False
|
||||
debug: bool = False
|
||||
offline: bool = False
|
||||
|
|
@ -62,7 +62,7 @@ class Arguments:
|
|||
class ArchConfig:
|
||||
version: str | None = None
|
||||
locale_config: LocaleConfiguration | None = None
|
||||
archinstall_language: Language = field(default_factory=lambda: translation_handler.get_language_by_abbr('en'))
|
||||
archinstall_language: Language = field(default_factory=lambda: translation_handler.get_language_by_abbr("en"))
|
||||
disk_config: DiskLayoutConfiguration | None = None
|
||||
profile_config: ProfileConfiguration | None = None
|
||||
mirror_config: MirrorConfiguration | None = None
|
||||
|
|
@ -70,13 +70,13 @@ class ArchConfig:
|
|||
bootloader: Bootloader = field(default=Bootloader.get_default())
|
||||
uki: bool = False
|
||||
audio_config: AudioConfiguration | None = None
|
||||
hostname: str = 'archlinux'
|
||||
kernels: list[str] = field(default_factory=lambda: ['linux'])
|
||||
hostname: str = "archlinux"
|
||||
kernels: list[str] = field(default_factory=lambda: ["linux"])
|
||||
ntp: bool = True
|
||||
packages: list[str] = field(default_factory=list)
|
||||
parallel_downloads: int = 0
|
||||
swap: bool = True
|
||||
timezone: str = 'UTC'
|
||||
timezone: str = "UTC"
|
||||
services: list[str] = field(default_factory=list)
|
||||
custom_commands: list[str] = field(default_factory=list)
|
||||
|
||||
|
|
@ -87,133 +87,133 @@ class ArchConfig:
|
|||
|
||||
def unsafe_json(self) -> dict[str, Any]:
|
||||
config = {
|
||||
'users': [user.json() for user in self.users],
|
||||
'root_enc_password': self.root_enc_password.enc_password if self.root_enc_password else None,
|
||||
"users": [user.json() for user in self.users],
|
||||
"root_enc_password": self.root_enc_password.enc_password if self.root_enc_password else None,
|
||||
}
|
||||
|
||||
if self.disk_encryption and self.disk_encryption.encryption_password:
|
||||
config['encryption_password'] = self.disk_encryption.encryption_password.plaintext
|
||||
config["encryption_password"] = self.disk_encryption.encryption_password.plaintext
|
||||
|
||||
return config
|
||||
|
||||
def safe_json(self) -> dict[str, Any]:
|
||||
config: Any = {
|
||||
'version': self.version,
|
||||
'archinstall-language': self.archinstall_language.json(),
|
||||
'hostname': self.hostname,
|
||||
'kernels': self.kernels,
|
||||
'ntp': self.ntp,
|
||||
'packages': self.packages,
|
||||
'parallel_downloads': self.parallel_downloads,
|
||||
'swap': self.swap,
|
||||
'timezone': self.timezone,
|
||||
'services': self.services,
|
||||
'custom_commands': self.custom_commands,
|
||||
'bootloader': self.bootloader.json(),
|
||||
'audio_config': self.audio_config.json() if self.audio_config else None,
|
||||
"version": self.version,
|
||||
"archinstall-language": self.archinstall_language.json(),
|
||||
"hostname": self.hostname,
|
||||
"kernels": self.kernels,
|
||||
"ntp": self.ntp,
|
||||
"packages": self.packages,
|
||||
"parallel_downloads": self.parallel_downloads,
|
||||
"swap": self.swap,
|
||||
"timezone": self.timezone,
|
||||
"services": self.services,
|
||||
"custom_commands": self.custom_commands,
|
||||
"bootloader": self.bootloader.json(),
|
||||
"audio_config": self.audio_config.json() if self.audio_config else None,
|
||||
}
|
||||
|
||||
if self.locale_config:
|
||||
config['locale_config'] = self.locale_config.json()
|
||||
config["locale_config"] = self.locale_config.json()
|
||||
|
||||
if self.disk_config:
|
||||
config['disk_config'] = self.disk_config.json()
|
||||
config["disk_config"] = self.disk_config.json()
|
||||
|
||||
if self.disk_encryption:
|
||||
config['disk_encryption'] = self.disk_encryption.json()
|
||||
config["disk_encryption"] = self.disk_encryption.json()
|
||||
|
||||
if self.profile_config:
|
||||
config['profile_config'] = self.profile_config.json()
|
||||
config["profile_config"] = self.profile_config.json()
|
||||
|
||||
if self.mirror_config:
|
||||
config['mirror_config'] = self.mirror_config.json()
|
||||
config["mirror_config"] = self.mirror_config.json()
|
||||
|
||||
if self.network_config:
|
||||
config['network_config'] = self.network_config.json()
|
||||
config["network_config"] = self.network_config.json()
|
||||
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, args_config: dict[str, Any]) -> 'ArchConfig':
|
||||
def from_config(cls, args_config: dict[str, Any]) -> "ArchConfig":
|
||||
arch_config = ArchConfig()
|
||||
|
||||
arch_config.locale_config = LocaleConfiguration.parse_arg(args_config)
|
||||
|
||||
if archinstall_lang := args_config.get('archinstall-language', None):
|
||||
if archinstall_lang := args_config.get("archinstall-language", None):
|
||||
arch_config.archinstall_language = translation_handler.get_language_by_name(archinstall_lang)
|
||||
|
||||
if disk_config := args_config.get('disk_config', {}):
|
||||
if disk_config := args_config.get("disk_config", {}):
|
||||
arch_config.disk_config = DiskLayoutConfiguration.parse_arg(disk_config)
|
||||
|
||||
if profile_config := args_config.get('profile_config', None):
|
||||
if profile_config := args_config.get("profile_config", None):
|
||||
arch_config.profile_config = ProfileConfiguration.parse_arg(profile_config)
|
||||
|
||||
if mirror_config := args_config.get('mirror_config', None):
|
||||
if mirror_config := args_config.get("mirror_config", None):
|
||||
backwards_compatible_repo = []
|
||||
if additional_repositories := args_config.get('additional-repositories', []):
|
||||
if additional_repositories := args_config.get("additional-repositories", []):
|
||||
backwards_compatible_repo = [Repository(r) for r in additional_repositories]
|
||||
|
||||
arch_config.mirror_config = MirrorConfiguration.parse_args(
|
||||
mirror_config,
|
||||
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)
|
||||
|
||||
# DEPRECATED: backwards copatibility
|
||||
if users := args_config.get('!users', None):
|
||||
if users := args_config.get("!users", None):
|
||||
arch_config.users = User.parse_arguments(users)
|
||||
|
||||
if users := args_config.get('users', None):
|
||||
if users := args_config.get("users", None):
|
||||
arch_config.users = User.parse_arguments(users)
|
||||
|
||||
if bootloader_config := args_config.get('bootloader', None):
|
||||
if bootloader_config := args_config.get("bootloader", None):
|
||||
arch_config.bootloader = Bootloader.from_arg(bootloader_config)
|
||||
|
||||
if args_config.get('uki') and not arch_config.bootloader.has_uki_support():
|
||||
if args_config.get("uki") and not arch_config.bootloader.has_uki_support():
|
||||
arch_config.uki = False
|
||||
|
||||
if audio_config := args_config.get('audio_config', None):
|
||||
if audio_config := args_config.get("audio_config", None):
|
||||
arch_config.audio_config = AudioConfiguration.parse_arg(audio_config)
|
||||
|
||||
if args_config.get('disk_encryption', None) is not None and arch_config.disk_config is not None:
|
||||
if args_config.get("disk_encryption", None) is not None and arch_config.disk_config is not None:
|
||||
arch_config.disk_encryption = DiskEncryption.parse_arg(
|
||||
arch_config.disk_config,
|
||||
args_config['disk_encryption'],
|
||||
Password(plaintext=args_config.get('encryption_password', ''))
|
||||
args_config["disk_encryption"],
|
||||
Password(plaintext=args_config.get("encryption_password", "")),
|
||||
)
|
||||
|
||||
if hostname := args_config.get('hostname', ''):
|
||||
if hostname := args_config.get("hostname", ""):
|
||||
arch_config.hostname = hostname
|
||||
|
||||
if kernels := args_config.get('kernels', []):
|
||||
if kernels := args_config.get("kernels", []):
|
||||
arch_config.kernels = kernels
|
||||
|
||||
arch_config.ntp = args_config.get('ntp', True)
|
||||
arch_config.ntp = args_config.get("ntp", True)
|
||||
|
||||
if packages := args_config.get('packages', []):
|
||||
if packages := args_config.get("packages", []):
|
||||
arch_config.packages = packages
|
||||
|
||||
if parallel_downloads := args_config.get('parallel_downloads', 0):
|
||||
if parallel_downloads := args_config.get("parallel_downloads", 0):
|
||||
arch_config.parallel_downloads = parallel_downloads
|
||||
|
||||
arch_config.swap = args_config.get('swap', True)
|
||||
arch_config.swap = args_config.get("swap", True)
|
||||
|
||||
if timezone := args_config.get('timezone', 'UTC'):
|
||||
if timezone := args_config.get("timezone", "UTC"):
|
||||
arch_config.timezone = timezone
|
||||
|
||||
if services := args_config.get('services', []):
|
||||
if services := args_config.get("services", []):
|
||||
arch_config.services = services
|
||||
|
||||
# DEPRECATED: backwards compatibility
|
||||
if root_password := args_config.get('!root-password', None):
|
||||
if root_password := args_config.get("!root-password", None):
|
||||
arch_config.root_enc_password = Password(plaintext=root_password)
|
||||
|
||||
if enc_password := args_config.get('root_enc_password', None):
|
||||
if enc_password := args_config.get("root_enc_password", None):
|
||||
arch_config.root_enc_password = Password(enc_password=enc_password)
|
||||
|
||||
if custom_commands := args_config.get('custom_commands', []):
|
||||
if custom_commands := args_config.get("custom_commands", []):
|
||||
arch_config.custom_commands = custom_commands
|
||||
|
||||
return arch_config
|
||||
|
|
@ -245,9 +245,9 @@ class ArchConfigHandler:
|
|||
|
||||
def _get_version(self) -> str:
|
||||
try:
|
||||
return version('archinstall')
|
||||
return version("archinstall")
|
||||
except Exception:
|
||||
return 'Archinstall version not found'
|
||||
return "Archinstall version not found"
|
||||
|
||||
def _define_arguments(self) -> ArgumentParser:
|
||||
parser = ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
|
@ -256,119 +256,119 @@ class ArchConfigHandler:
|
|||
"--version",
|
||||
action="version",
|
||||
default=False,
|
||||
version="%(prog)s " + self._get_version()
|
||||
version="%(prog)s " + self._get_version(),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
type=Path,
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="JSON configuration file"
|
||||
help="JSON configuration file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config-url",
|
||||
type=str,
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="Url to a JSON configuration file"
|
||||
help="Url to a JSON configuration file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--creds",
|
||||
type=Path,
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="JSON credentials configuration file"
|
||||
help="JSON credentials configuration file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--creds-url",
|
||||
type=str,
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="Url to a JSON credentials configuration file"
|
||||
help="Url to a JSON credentials configuration file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--creds-decryption-key",
|
||||
type=str,
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="Decryption key for credentials file"
|
||||
help="Decryption key for credentials file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--silent",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored"
|
||||
help="WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
"--dry_run",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Generates a configuration file and then exits instead of performing an installation"
|
||||
help="Generates a configuration file and then exits instead of performing an installation",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--script",
|
||||
default="guided",
|
||||
nargs="?",
|
||||
help="Script to run for installation",
|
||||
type=str
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mount-point",
|
||||
"--mount_point",
|
||||
type=Path,
|
||||
nargs="?",
|
||||
default=Path('/mnt'),
|
||||
help="Define an alternate mount point for installation"
|
||||
default=Path("/mnt"),
|
||||
help="Define an alternate mount point for installation",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-ntp",
|
||||
action="store_true",
|
||||
help="Disables NTP checks during installation",
|
||||
default=False
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Adds debug info into the log"
|
||||
help="Adds debug info into the log",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--offline",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Disabled online upstream services such as package search and key-ring auto update."
|
||||
help="Disabled online upstream services such as package search and key-ring auto update.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-pkg-lookups",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Disabled package validation specifically prior to starting installation."
|
||||
help="Disabled package validation specifically prior to starting installation.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--plugin",
|
||||
nargs="?",
|
||||
type=str,
|
||||
default=None,
|
||||
help='File path to a plugin to load'
|
||||
help="File path to a plugin to load",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-version-check",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Skip the version check when running archinstall"
|
||||
help="Skip the version check when running archinstall",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--advanced",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Enabled advanced options"
|
||||
help="Enabled advanced options",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Enabled verbose options"
|
||||
help="Enabled verbose options",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
|
@ -390,8 +390,8 @@ class ArchConfigHandler:
|
|||
load_plugin(plugin_path)
|
||||
|
||||
if args.creds_decryption_key is None:
|
||||
if os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY'):
|
||||
args.creds_decryption_key = os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY')
|
||||
if os.environ.get("ARCHINSTALL_CREDS_DECRYPTION_KEY"):
|
||||
args.creds_decryption_key = os.environ.get("ARCHINSTALL_CREDS_DECRYPTION_KEY")
|
||||
|
||||
return args
|
||||
|
||||
|
|
@ -423,30 +423,30 @@ class ArchConfigHandler:
|
|||
return config
|
||||
|
||||
def _process_creds_data(self, creds_data: str) -> dict[str, Any] | None:
|
||||
if creds_data.startswith('$'): # encrypted data
|
||||
if creds_data.startswith("$"): # encrypted data
|
||||
if self._args.creds_decryption_key is not None:
|
||||
try:
|
||||
creds_data = decrypt(creds_data, self._args.creds_decryption_key)
|
||||
return json.loads(creds_data)
|
||||
except ValueError as err:
|
||||
if 'Invalid password' in str(err):
|
||||
error(str(_('Incorrect credentials file decryption password')))
|
||||
if "Invalid password" in str(err):
|
||||
error(str(_("Incorrect credentials file decryption password")))
|
||||
exit(1)
|
||||
else:
|
||||
debug(f'Error decrypting credentials file: {err}')
|
||||
debug(f"Error decrypting credentials file: {err}")
|
||||
raise err from err
|
||||
else:
|
||||
incorrect_password = False
|
||||
|
||||
with Tui():
|
||||
while True:
|
||||
header = str(_('Incorrect password')) if incorrect_password else None
|
||||
header = str(_("Incorrect password")) if incorrect_password else None
|
||||
|
||||
decryption_pwd = get_password(
|
||||
text=str(_('Credentials file decryption password')),
|
||||
text=str(_("Credentials file decryption password")),
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
skip_confirmation=True
|
||||
skip_confirmation=True,
|
||||
)
|
||||
|
||||
if not decryption_pwd:
|
||||
|
|
@ -456,11 +456,11 @@ class ArchConfigHandler:
|
|||
creds_data = decrypt(creds_data, decryption_pwd.plaintext)
|
||||
break
|
||||
except ValueError as err:
|
||||
if 'Invalid password' in str(err):
|
||||
debug('Incorrect credentials file decryption password')
|
||||
if "Invalid password" in str(err):
|
||||
debug("Incorrect credentials file decryption password")
|
||||
incorrect_password = True
|
||||
else:
|
||||
debug(f'Error decrypting credentials file: {err}')
|
||||
debug(f"Error decrypting credentials file: {err}")
|
||||
raise err from err
|
||||
|
||||
return json.loads(creds_data)
|
||||
|
|
@ -468,13 +468,13 @@ class ArchConfigHandler:
|
|||
def _fetch_from_url(self, url: str) -> str:
|
||||
if urllib.parse.urlparse(url).scheme:
|
||||
try:
|
||||
req = Request(url, headers={'User-Agent': 'ArchInstall'})
|
||||
req = Request(url, headers={"User-Agent": "ArchInstall"})
|
||||
with urlopen(req) as resp:
|
||||
return resp.read().decode('utf-8')
|
||||
return resp.read().decode("utf-8")
|
||||
except urllib.error.HTTPError as err:
|
||||
error(f"Could not fetch JSON from {url}: {err}")
|
||||
else:
|
||||
error('Not a valid url')
|
||||
error("Not a valid url")
|
||||
|
||||
exit(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ from .storage import storage
|
|||
class Boot:
|
||||
def __init__(self, installation: Installer):
|
||||
self.instance = installation
|
||||
self.container_name = 'archinstall'
|
||||
self.container_name = "archinstall"
|
||||
self.session: SysCommandWorker | None = None
|
||||
self.ready = False
|
||||
|
||||
def __enter__(self) -> 'Boot':
|
||||
if (existing_session := storage.get('active_boot', None)) and existing_session.instance != self.instance:
|
||||
def __enter__(self) -> "Boot":
|
||||
if (existing_session := storage.get("active_boot", None)) and existing_session.instance != self.instance:
|
||||
raise KeyError("Archinstall only supports booting up one instance and another session is already active.")
|
||||
|
||||
if existing_session:
|
||||
|
|
@ -25,22 +25,26 @@ class Boot:
|
|||
else:
|
||||
# '-P' or --console=pipe could help us not having to do a bunch
|
||||
# of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual.
|
||||
self.session = SysCommandWorker([
|
||||
'systemd-nspawn',
|
||||
'-D', str(self.instance.target),
|
||||
'--timezone=off',
|
||||
'-b',
|
||||
'--no-pager',
|
||||
'--machine', self.container_name
|
||||
])
|
||||
self.session = SysCommandWorker(
|
||||
[
|
||||
"systemd-nspawn",
|
||||
"-D",
|
||||
str(self.instance.target),
|
||||
"--timezone=off",
|
||||
"-b",
|
||||
"--no-pager",
|
||||
"--machine",
|
||||
self.container_name,
|
||||
]
|
||||
)
|
||||
|
||||
if not self.ready and self.session:
|
||||
while self.session.is_alive():
|
||||
if b' login:' in self.session:
|
||||
if b" login:" in self.session:
|
||||
self.ready = True
|
||||
break
|
||||
|
||||
storage['active_boot'] = self
|
||||
storage["active_boot"] = self
|
||||
return self
|
||||
|
||||
def __exit__(self, *args: str, **kwargs: str) -> None:
|
||||
|
|
@ -50,14 +54,14 @@ class Boot:
|
|||
if len(args) >= 2 and args[1]:
|
||||
error(
|
||||
args[1],
|
||||
f"The error above occurred in a temporary boot-up of the installation {self.instance}"
|
||||
f"The error above occurred in a temporary boot-up of the installation {self.instance}",
|
||||
)
|
||||
|
||||
shutdown = None
|
||||
shutdown_exit_code: int | None = -1
|
||||
|
||||
try:
|
||||
shutdown = SysCommand(f'systemd-run --machine={self.container_name} --pty shutdown now')
|
||||
shutdown = SysCommand(f"systemd-run --machine={self.container_name} --pty shutdown now")
|
||||
except SysCallError as err:
|
||||
shutdown_exit_code = err.exit_code
|
||||
|
||||
|
|
@ -69,13 +73,13 @@ class Boot:
|
|||
shutdown_exit_code = shutdown.exit_code
|
||||
|
||||
if self.session and (self.session.exit_code == 0 or shutdown_exit_code == 0):
|
||||
storage['active_boot'] = None
|
||||
storage["active_boot"] = None
|
||||
else:
|
||||
session_exit_code = self.session.exit_code if self.session else -1
|
||||
|
||||
raise SysCallError(
|
||||
f"Could not shut down temporary boot of {self.instance}: {session_exit_code}/{shutdown_exit_code}",
|
||||
exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code]))
|
||||
exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code])),
|
||||
)
|
||||
|
||||
def __iter__(self) -> Iterator[bytes]:
|
||||
|
|
@ -95,7 +99,7 @@ class Boot:
|
|||
return self.session.is_alive()
|
||||
|
||||
def SysCommand(self, cmd: list[str], *args, **kwargs) -> SysCommand:
|
||||
if cmd[0][0] != '/' and cmd[0][:2] != './':
|
||||
if cmd[0][0] != "/" and cmd[0][:2] != "./":
|
||||
# This check is also done in SysCommand & SysCommandWorker.
|
||||
# However, that check is done for `machinectl` and not for our chroot command.
|
||||
# So this wrapper for SysCommand will do this additionally.
|
||||
|
|
@ -105,7 +109,7 @@ class Boot:
|
|||
return SysCommand(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs)
|
||||
|
||||
def SysCommandWorker(self, cmd: list[str], *args, **kwargs) -> SysCommandWorker:
|
||||
if cmd[0][0] != '/' and cmd[0][:2] != './':
|
||||
if cmd[0][0] != "/" and cmd[0][:2] != "./":
|
||||
cmd[0] = locate_binary(cmd[0])
|
||||
|
||||
return SysCommandWorker(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ class ConfigurationOutput:
|
|||
"""
|
||||
|
||||
self._config = config
|
||||
self._default_save_path = storage.get('LOG_PATH', Path('.'))
|
||||
self._user_config_file = Path('user_configuration.json')
|
||||
self._user_creds_file = Path('user_credentials.json')
|
||||
self._default_save_path = storage.get("LOG_PATH", Path("."))
|
||||
self._user_config_file = Path("user_configuration.json")
|
||||
self._user_creds_file = Path("user_credentials.json")
|
||||
|
||||
@property
|
||||
def user_configuration_file(self) -> Path:
|
||||
|
|
@ -61,8 +61,8 @@ class ConfigurationOutput:
|
|||
debug(self.user_config_to_json())
|
||||
|
||||
def confirm_config(self) -> bool:
|
||||
header = f'{_("The specified configuration will be applied")}. '
|
||||
header += str(_('Would you like to continue?')) + '\n'
|
||||
header = f"{_('The specified configuration will be applied')}. "
|
||||
header += str(_("Would you like to continue?")) + "\n"
|
||||
|
||||
with Tui():
|
||||
group = MenuItemGroup.yes_no()
|
||||
|
|
@ -76,9 +76,9 @@ class ConfigurationOutput:
|
|||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
allow_skip=False,
|
||||
preview_size='auto',
|
||||
preview_size="auto",
|
||||
preview_style=PreviewStyle.BOTTOM,
|
||||
preview_frame=FrameProperties.max(str(_('Configuration')))
|
||||
preview_frame=FrameProperties.max(str(_("Configuration"))),
|
||||
).run()
|
||||
|
||||
if result.item() != MenuItem.yes():
|
||||
|
|
@ -90,8 +90,8 @@ class ConfigurationOutput:
|
|||
dest_path_ok = dest_path.exists() and dest_path.is_dir()
|
||||
if not dest_path_ok:
|
||||
warn(
|
||||
f'Destination directory {dest_path.resolve()} does not exist or is not a directory\n.',
|
||||
'Configuration files can not be saved'
|
||||
f"Destination directory {dest_path.resolve()} does not exist or is not a directory\n.",
|
||||
"Configuration files can not be saved",
|
||||
)
|
||||
return dest_path_ok
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ class ConfigurationOutput:
|
|||
def save_user_creds(
|
||||
self,
|
||||
dest_path: Path,
|
||||
password: str | None = None
|
||||
password: str | None = None,
|
||||
) -> None:
|
||||
data = self.user_credentials_to_json()
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ class ConfigurationOutput:
|
|||
self,
|
||||
dest_path: Path | None = None,
|
||||
creds: bool = False,
|
||||
password: str | None = None
|
||||
password: str | None = None,
|
||||
) -> None:
|
||||
save_path = dest_path or self._default_save_path
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ def save_config(config: ArchConfig) -> None:
|
|||
output = [str(config_output.user_configuration_file)]
|
||||
config_output.user_credentials_to_json()
|
||||
output.append(str(config_output.user_credentials_file))
|
||||
return '\n'.join(output)
|
||||
return "\n".join(output)
|
||||
return None
|
||||
|
||||
config_output = ConfigurationOutput(config)
|
||||
|
|
@ -153,27 +153,27 @@ def save_config(config: ArchConfig) -> None:
|
|||
MenuItem(
|
||||
str(_("Save user configuration (including disk layout)")),
|
||||
value="user_config",
|
||||
preview_action=preview
|
||||
preview_action=preview,
|
||||
),
|
||||
MenuItem(
|
||||
str(_("Save user credentials")),
|
||||
value="user_creds",
|
||||
preview_action=preview
|
||||
preview_action=preview,
|
||||
),
|
||||
MenuItem(
|
||||
str(_("Save all")),
|
||||
value="all",
|
||||
preview_action=preview
|
||||
)
|
||||
preview_action=preview,
|
||||
),
|
||||
]
|
||||
|
||||
group = MenuItemGroup(items)
|
||||
result = SelectMenu[str](
|
||||
group,
|
||||
allow_skip=True,
|
||||
preview_frame=FrameProperties.max(str(_('Configuration'))),
|
||||
preview_size='auto',
|
||||
preview_style=PreviewStyle.RIGHT
|
||||
preview_frame=FrameProperties.max(str(_("Configuration"))),
|
||||
preview_size="auto",
|
||||
preview_style=PreviewStyle.RIGHT,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -182,15 +182,15 @@ def save_config(config: ArchConfig) -> None:
|
|||
case ResultType.Selection:
|
||||
save_option = result.get_value()
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
readline.set_completer_delims("\t\n=")
|
||||
readline.parse_and_bind("tab: complete")
|
||||
|
||||
dest_path = prompt_dir(
|
||||
str(_('Directory')),
|
||||
str(_('Enter a directory for the configuration(s) to be saved (tab completion enabled)')) + '\n',
|
||||
allow_skip=True
|
||||
str(_("Directory")),
|
||||
str(_("Enter a directory for the configuration(s) to be saved (tab completion enabled)")) + "\n",
|
||||
allow_skip=True,
|
||||
)
|
||||
|
||||
if not dest_path:
|
||||
|
|
@ -207,7 +207,7 @@ def save_config(config: ArchConfig) -> None:
|
|||
allow_skip=False,
|
||||
alignment=Alignment.CENTER,
|
||||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -217,7 +217,7 @@ def save_config(config: ArchConfig) -> None:
|
|||
|
||||
debug(f"Saving configuration files to {dest_path.absolute()}")
|
||||
|
||||
header = str(_('Do you want to encrypt the user_credentials.json file?'))
|
||||
header = str(_("Do you want to encrypt the user_credentials.json file?"))
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.focus_item = MenuItem.no()
|
||||
|
|
@ -228,7 +228,7 @@ def save_config(config: ArchConfig) -> None:
|
|||
allow_skip=False,
|
||||
alignment=Alignment.CENTER,
|
||||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
).run()
|
||||
|
||||
enc_password: str | None = None
|
||||
|
|
@ -236,8 +236,8 @@ def save_config(config: ArchConfig) -> None:
|
|||
case ResultType.Selection:
|
||||
if result.item() == MenuItem.yes():
|
||||
password = get_password(
|
||||
text=str(_('Credentials file encryption password')),
|
||||
allow_skip=True
|
||||
text=str(_("Credentials file encryption password")),
|
||||
allow_skip=True,
|
||||
)
|
||||
|
||||
if password:
|
||||
|
|
|
|||
|
|
@ -16,19 +16,19 @@ libcrypt.crypt.restype = ctypes.c_char_p
|
|||
libcrypt.crypt_gensalt.argtypes = [ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p, ctypes.c_int]
|
||||
libcrypt.crypt_gensalt.restype = ctypes.c_char_p
|
||||
|
||||
LOGIN_DEFS = Path('/etc/login.defs')
|
||||
LOGIN_DEFS = Path("/etc/login.defs")
|
||||
|
||||
|
||||
def _search_login_defs(key: str) -> str | None:
|
||||
defs = LOGIN_DEFS.read_text()
|
||||
for line in defs.split('\n'):
|
||||
for line in defs.split("\n"):
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('#'):
|
||||
if line.startswith("#"):
|
||||
continue
|
||||
|
||||
if line.startswith(key):
|
||||
value = line.split(' ')[1]
|
||||
value = line.split(" ")[1]
|
||||
return value
|
||||
|
||||
return None
|
||||
|
|
@ -36,12 +36,12 @@ def _search_login_defs(key: str) -> str | None:
|
|||
|
||||
def crypt_gen_salt(prefix: str | bytes, rounds: int) -> bytes:
|
||||
if isinstance(prefix, str):
|
||||
prefix = prefix.encode('utf-8')
|
||||
prefix = prefix.encode("utf-8")
|
||||
|
||||
setting = libcrypt.crypt_gensalt(prefix, rounds, None, 0)
|
||||
|
||||
if setting is None:
|
||||
raise ValueError(f'crypt_gensalt() returned NULL for prefix {prefix!r} and rounds {rounds}')
|
||||
raise ValueError(f"crypt_gensalt() returned NULL for prefix {prefix!r} and rounds {rounds}")
|
||||
|
||||
return setting
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ def crypt_yescrypt(plaintext: str) -> str:
|
|||
shows that the hashing rounds are determined from YESCRYPT_COST_FACTOR in /etc/login.defs
|
||||
If no value was specified (or commented out) a default of 5 is choosen
|
||||
"""
|
||||
value = _search_login_defs('YESCRYPT_COST_FACTOR')
|
||||
value = _search_login_defs("YESCRYPT_COST_FACTOR")
|
||||
if value is not None:
|
||||
rounds = int(value)
|
||||
if rounds < 3:
|
||||
|
|
@ -63,17 +63,17 @@ def crypt_yescrypt(plaintext: str) -> str:
|
|||
else:
|
||||
rounds = 5
|
||||
|
||||
debug(f'Creating yescrypt hash with rounds {rounds}')
|
||||
debug(f"Creating yescrypt hash with rounds {rounds}")
|
||||
|
||||
enc_plaintext = plaintext.encode('utf-8')
|
||||
salt = crypt_gen_salt('$y$', rounds)
|
||||
enc_plaintext = plaintext.encode("utf-8")
|
||||
salt = crypt_gen_salt("$y$", rounds)
|
||||
|
||||
crypt_hash = libcrypt.crypt(enc_plaintext, salt)
|
||||
|
||||
if crypt_hash is None:
|
||||
raise ValueError('crypt() returned NULL')
|
||||
raise ValueError("crypt() returned NULL")
|
||||
|
||||
return crypt_hash.decode('utf-8')
|
||||
return crypt_hash.decode("utf-8")
|
||||
|
||||
|
||||
def _get_fernet(salt: bytes, password: str) -> Fernet:
|
||||
|
|
@ -90,8 +90,8 @@ def _get_fernet(salt: bytes, password: str) -> Fernet:
|
|||
|
||||
key = base64.urlsafe_b64encode(
|
||||
kdf.derive(
|
||||
password.encode('utf-8')
|
||||
)
|
||||
password.encode("utf-8"),
|
||||
),
|
||||
)
|
||||
|
||||
return Fernet(key)
|
||||
|
|
@ -100,26 +100,26 @@ def _get_fernet(salt: bytes, password: str) -> Fernet:
|
|||
def encrypt(password: str, data: str) -> str:
|
||||
salt = os.urandom(16)
|
||||
f = _get_fernet(salt, password)
|
||||
token = f.encrypt(data.encode('utf-8'))
|
||||
token = f.encrypt(data.encode("utf-8"))
|
||||
|
||||
encoded_token = base64.urlsafe_b64encode(token).decode('utf-8')
|
||||
encoded_salt = base64.urlsafe_b64encode(salt).decode('utf-8')
|
||||
encoded_token = base64.urlsafe_b64encode(token).decode("utf-8")
|
||||
encoded_salt = base64.urlsafe_b64encode(salt).decode("utf-8")
|
||||
|
||||
return f'$argon2id${encoded_salt}${encoded_token}'
|
||||
return f"$argon2id${encoded_salt}${encoded_token}"
|
||||
|
||||
|
||||
def decrypt(data: str, password: str):
|
||||
_, algo, encoded_salt, encoded_token = data.split('$')
|
||||
_, algo, encoded_salt, encoded_token = data.split("$")
|
||||
salt = base64.urlsafe_b64decode(encoded_salt)
|
||||
token = base64.urlsafe_b64decode(encoded_token)
|
||||
|
||||
if algo != 'argon2id':
|
||||
raise ValueError(f'Unsupported algorithm {algo!r}')
|
||||
if algo != "argon2id":
|
||||
raise ValueError(f"Unsupported algorithm {algo!r}")
|
||||
|
||||
f = _get_fernet(salt, password)
|
||||
try:
|
||||
decrypted = f.decrypt(token)
|
||||
except InvalidToken:
|
||||
raise ValueError('Invalid password')
|
||||
raise ValueError("Invalid password")
|
||||
|
||||
return decrypted.decode('utf-8')
|
||||
return decrypted.decode("utf-8")
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ from .utils import (
|
|||
|
||||
|
||||
class DeviceHandler:
|
||||
_TMP_BTRFS_MOUNT = Path('/mnt/arch_btrfs')
|
||||
_TMP_BTRFS_MOUNT = Path("/mnt/arch_btrfs")
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._devices: dict[Path, BDevice] = {}
|
||||
|
|
@ -73,16 +73,16 @@ class DeviceHandler:
|
|||
devices = getAllDevices()
|
||||
devices.extend(self.get_loop_devices())
|
||||
|
||||
archiso_mountpoint = Path('/run/archiso/airootfs')
|
||||
archiso_mountpoint = Path("/run/archiso/airootfs")
|
||||
|
||||
for device in devices:
|
||||
dev_lsblk_info = find_lsblk_info(device.path, all_lsblk_info)
|
||||
|
||||
if not dev_lsblk_info:
|
||||
debug(f'Device lsblk info not found: {device.path}')
|
||||
debug(f"Device lsblk info not found: {device.path}")
|
||||
continue
|
||||
|
||||
if dev_lsblk_info.type == 'rom':
|
||||
if dev_lsblk_info.type == "rom":
|
||||
continue
|
||||
|
||||
# exclude archiso loop device
|
||||
|
|
@ -95,7 +95,7 @@ class DeviceHandler:
|
|||
else:
|
||||
disk = freshDisk(device, self.partition_table.value)
|
||||
except DiskException as err:
|
||||
debug(f'Unable to get disk from {device.path}: {err}')
|
||||
debug(f"Unable to get disk from {device.path}: {err}")
|
||||
continue
|
||||
|
||||
device_info = _DeviceInfo.from_disk(disk)
|
||||
|
|
@ -105,7 +105,7 @@ class DeviceHandler:
|
|||
lsblk_info = find_lsblk_info(partition.path, dev_lsblk_info.children)
|
||||
|
||||
if not lsblk_info:
|
||||
debug(f'Partition lsblk info not found: {partition.path}')
|
||||
debug(f"Partition lsblk info not found: {partition.path}")
|
||||
continue
|
||||
|
||||
fs_type = self._determine_fs_type(partition, lsblk_info)
|
||||
|
|
@ -119,8 +119,8 @@ class DeviceHandler:
|
|||
partition,
|
||||
lsblk_info,
|
||||
fs_type,
|
||||
subvol_infos
|
||||
)
|
||||
subvol_infos,
|
||||
),
|
||||
)
|
||||
|
||||
block_device = BDevice(disk, device_info, partition_infos)
|
||||
|
|
@ -133,20 +133,20 @@ class DeviceHandler:
|
|||
devices = []
|
||||
|
||||
try:
|
||||
loop_devices = SysCommand(['losetup', '-a'])
|
||||
loop_devices = SysCommand(["losetup", "-a"])
|
||||
except SysCallError as err:
|
||||
debug(f'Failed to get loop devices: {err}')
|
||||
debug(f"Failed to get loop devices: {err}")
|
||||
else:
|
||||
for ld_info in str(loop_devices).splitlines():
|
||||
try:
|
||||
loop_device_path, _ = ld_info.split(':', maxsplit=1)
|
||||
loop_device_path, _ = ld_info.split(":", maxsplit=1)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
try:
|
||||
loop_device = getDevice(loop_device_path)
|
||||
except IOException as err:
|
||||
debug(f'Failed to get loop device: {err}')
|
||||
debug(f"Failed to get loop device: {err}")
|
||||
else:
|
||||
devices.append(loop_device)
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ class DeviceHandler:
|
|||
def _determine_fs_type(
|
||||
self,
|
||||
partition: Partition,
|
||||
lsblk_info: LsblkInfo | None = None
|
||||
lsblk_info: LsblkInfo | None = None,
|
||||
) -> FilesystemType | None:
|
||||
try:
|
||||
if partition.fileSystem:
|
||||
|
|
@ -166,7 +166,7 @@ class DeviceHandler:
|
|||
return FilesystemType(lsblk_info.fstype) if lsblk_info.fstype else None
|
||||
return None
|
||||
except ValueError:
|
||||
debug(f'Could not determine the filesystem: {partition.fileSystem}')
|
||||
debug(f"Could not determine the filesystem: {partition.fileSystem}")
|
||||
|
||||
return None
|
||||
|
||||
|
|
@ -189,15 +189,12 @@ class DeviceHandler:
|
|||
|
||||
def get_parent_device_path(self, dev_path: Path) -> Path:
|
||||
lsblk = get_lsblk_info(dev_path)
|
||||
return Path(f'/dev/{lsblk.pkname}')
|
||||
return Path(f"/dev/{lsblk.pkname}")
|
||||
|
||||
def get_unique_path_for_device(self, dev_path: Path) -> Path | None:
|
||||
paths = Path('/dev/disk/by-id').glob('*')
|
||||
paths = Path("/dev/disk/by-id").glob("*")
|
||||
linked_targets = {p.resolve(): p for p in paths}
|
||||
linked_wwn_targets = {
|
||||
p: linked_targets[p] for p in linked_targets
|
||||
if p.name.startswith('wwn-') or p.name.startswith('nvme-eui.')
|
||||
}
|
||||
linked_wwn_targets = {p: linked_targets[p] for p in linked_targets if p.name.startswith("wwn-") or p.name.startswith("nvme-eui.")}
|
||||
|
||||
if dev_path in linked_wwn_targets:
|
||||
return linked_wwn_targets[dev_path]
|
||||
|
|
@ -214,7 +211,7 @@ class DeviceHandler:
|
|||
def get_btrfs_info(
|
||||
self,
|
||||
dev_path: Path,
|
||||
lsblk_info: LsblkInfo | None = None
|
||||
lsblk_info: LsblkInfo | None = None,
|
||||
) -> list[_BtrfsSubvolumeInfo]:
|
||||
if not lsblk_info:
|
||||
lsblk_info = get_lsblk_info(dev_path)
|
||||
|
|
@ -237,9 +234,9 @@ class DeviceHandler:
|
|||
mountpoint = Path(common_path)
|
||||
|
||||
try:
|
||||
result = SysCommand(f'btrfs subvolume list {mountpoint}').decode()
|
||||
result = SysCommand(f"btrfs subvolume list {mountpoint}").decode()
|
||||
except SysCallError as err:
|
||||
debug(f'Failed to read btrfs subvolume information: {err}')
|
||||
debug(f"Failed to read btrfs subvolume information: {err}")
|
||||
return subvol_infos
|
||||
|
||||
# It is assumed that lsblk will contain the fields as
|
||||
|
|
@ -253,8 +250,8 @@ class DeviceHandler:
|
|||
for line in result.splitlines():
|
||||
# expected output format:
|
||||
# ID 257 gen 8 top level 5 path @home
|
||||
name = Path(line.split(' ')[-1])
|
||||
sub_vol_mountpoint = btrfs_subvol_info.get('/' / name, None)
|
||||
name = Path(line.split(" ")[-1])
|
||||
sub_vol_mountpoint = btrfs_subvol_info.get("/" / name, None)
|
||||
subvol_infos.append(_BtrfsSubvolumeInfo(name, sub_vol_mountpoint))
|
||||
|
||||
if not lsblk_info.mountpoint:
|
||||
|
|
@ -266,7 +263,7 @@ class DeviceHandler:
|
|||
self,
|
||||
fs_type: FilesystemType,
|
||||
path: Path,
|
||||
additional_parted_options: list[str] = []
|
||||
additional_parted_options: list[str] = [],
|
||||
) -> None:
|
||||
mkfs_type = fs_type.value
|
||||
command = None
|
||||
|
|
@ -275,33 +272,33 @@ class DeviceHandler:
|
|||
match fs_type:
|
||||
case FilesystemType.Btrfs | FilesystemType.F2fs | FilesystemType.Xfs:
|
||||
# Force overwrite
|
||||
options.append('-f')
|
||||
options.append("-f")
|
||||
case FilesystemType.Ext2 | FilesystemType.Ext3 | FilesystemType.Ext4:
|
||||
# Force create
|
||||
options.append('-F')
|
||||
options.append("-F")
|
||||
case FilesystemType.Fat12 | FilesystemType.Fat16 | FilesystemType.Fat32:
|
||||
mkfs_type = 'fat'
|
||||
mkfs_type = "fat"
|
||||
# Set FAT size
|
||||
options.extend(('-F', fs_type.value.removeprefix(mkfs_type)))
|
||||
options.extend(("-F", fs_type.value.removeprefix(mkfs_type)))
|
||||
case FilesystemType.Ntfs:
|
||||
# Skip zeroing and bad sector check
|
||||
options.append('--fast')
|
||||
options.append("--fast")
|
||||
case FilesystemType.LinuxSwap:
|
||||
command = "mkswap"
|
||||
case _:
|
||||
raise UnknownFilesystemFormat(f'Filetype "{fs_type.value}" is not supported')
|
||||
|
||||
if not command:
|
||||
command = f'mkfs.{mkfs_type}'
|
||||
command = f"mkfs.{mkfs_type}"
|
||||
|
||||
cmd = [command, *options, *additional_parted_options, str(path)]
|
||||
|
||||
debug('Formatting filesystem:', ' '.join(cmd))
|
||||
debug("Formatting filesystem:", " ".join(cmd))
|
||||
|
||||
try:
|
||||
SysCommand(cmd)
|
||||
except SysCallError as err:
|
||||
msg = f'Could not format {path} with {fs_type.value}: {err.message}'
|
||||
msg = f"Could not format {path} with {fs_type.value}: {err.message}"
|
||||
error(msg)
|
||||
raise DiskError(msg) from err
|
||||
|
||||
|
|
@ -310,12 +307,12 @@ class DeviceHandler:
|
|||
dev_path: Path,
|
||||
mapper_name: str | None,
|
||||
enc_password: Password | None,
|
||||
lock_after_create: bool = True
|
||||
lock_after_create: bool = True,
|
||||
) -> Luks2:
|
||||
luks_handler = Luks2(
|
||||
dev_path,
|
||||
mapper_name=mapper_name,
|
||||
password=enc_password
|
||||
password=enc_password,
|
||||
)
|
||||
|
||||
key_file = luks_handler.encrypt()
|
||||
|
|
@ -325,10 +322,10 @@ class DeviceHandler:
|
|||
luks_handler.unlock(key_file=key_file)
|
||||
|
||||
if not luks_handler.mapper_dev:
|
||||
raise DiskError('Failed to unlock luks device')
|
||||
raise DiskError("Failed to unlock luks device")
|
||||
|
||||
if lock_after_create:
|
||||
debug(f'luks2 locking device: {dev_path}')
|
||||
debug(f"luks2 locking device: {dev_path}")
|
||||
luks_handler.lock()
|
||||
|
||||
return luks_handler
|
||||
|
|
@ -338,15 +335,15 @@ class DeviceHandler:
|
|||
dev_path: Path,
|
||||
mapper_name: str | None,
|
||||
fs_type: FilesystemType,
|
||||
enc_conf: DiskEncryption
|
||||
enc_conf: DiskEncryption,
|
||||
) -> None:
|
||||
if not enc_conf.encryption_password:
|
||||
raise ValueError('No encryption password provided')
|
||||
raise ValueError("No encryption password provided")
|
||||
|
||||
luks_handler = Luks2(
|
||||
dev_path,
|
||||
mapper_name=mapper_name,
|
||||
password=enc_conf.encryption_password
|
||||
password=enc_conf.encryption_password,
|
||||
)
|
||||
|
||||
key_file = luks_handler.encrypt()
|
||||
|
|
@ -356,72 +353,69 @@ class DeviceHandler:
|
|||
luks_handler.unlock(key_file=key_file)
|
||||
|
||||
if not luks_handler.mapper_dev:
|
||||
raise DiskError('Failed to unlock luks device')
|
||||
raise DiskError("Failed to unlock luks device")
|
||||
|
||||
info(f'luks2 formatting mapper dev: {luks_handler.mapper_dev}')
|
||||
info(f"luks2 formatting mapper dev: {luks_handler.mapper_dev}")
|
||||
self.format(fs_type, luks_handler.mapper_dev)
|
||||
|
||||
info(f'luks2 locking device: {dev_path}')
|
||||
info(f"luks2 locking device: {dev_path}")
|
||||
luks_handler.lock()
|
||||
|
||||
def _lvm_info(
|
||||
self,
|
||||
cmd: str,
|
||||
info_type: Literal['lv', 'vg', 'pvseg']
|
||||
info_type: Literal["lv", "vg", "pvseg"],
|
||||
) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None:
|
||||
raw_info = SysCommand(cmd).decode().split('\n')
|
||||
raw_info = SysCommand(cmd).decode().split("\n")
|
||||
|
||||
# for whatever reason the output sometimes contains
|
||||
# "File descriptor X leaked leaked on vgs invocation
|
||||
data = '\n'.join([raw for raw in raw_info if 'File descriptor' not in raw])
|
||||
data = "\n".join([raw for raw in raw_info if "File descriptor" not in raw])
|
||||
|
||||
debug(f'LVM info: {data}')
|
||||
debug(f"LVM info: {data}")
|
||||
|
||||
reports = json.loads(data)
|
||||
|
||||
for report in reports['report']:
|
||||
for report in reports["report"]:
|
||||
if len(report[info_type]) != 1:
|
||||
raise ValueError('Report does not contain any entry')
|
||||
raise ValueError("Report does not contain any entry")
|
||||
|
||||
entry = report[info_type][0]
|
||||
|
||||
match info_type:
|
||||
case 'pvseg':
|
||||
case "pvseg":
|
||||
return LvmPVInfo(
|
||||
pv_name=Path(entry['pv_name']),
|
||||
lv_name=entry['lv_name'],
|
||||
vg_name=entry['vg_name'],
|
||||
pv_name=Path(entry["pv_name"]),
|
||||
lv_name=entry["lv_name"],
|
||||
vg_name=entry["vg_name"],
|
||||
)
|
||||
case 'lv':
|
||||
case "lv":
|
||||
return LvmVolumeInfo(
|
||||
lv_name=entry['lv_name'],
|
||||
vg_name=entry['vg_name'],
|
||||
lv_size=Size(int(entry['lv_size'][:-1]), Unit.B, SectorSize.default())
|
||||
lv_name=entry["lv_name"],
|
||||
vg_name=entry["vg_name"],
|
||||
lv_size=Size(int(entry["lv_size"][:-1]), Unit.B, SectorSize.default()),
|
||||
)
|
||||
case 'vg':
|
||||
case "vg":
|
||||
return LvmGroupInfo(
|
||||
vg_uuid=entry['vg_uuid'],
|
||||
vg_size=Size(int(entry['vg_size'][:-1]), Unit.B, SectorSize.default())
|
||||
vg_uuid=entry["vg_uuid"],
|
||||
vg_size=Size(int(entry["vg_size"][:-1]), Unit.B, SectorSize.default()),
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@overload
|
||||
def _lvm_info_with_retry(self, cmd: str, info_type: Literal['lv']) -> LvmVolumeInfo | None:
|
||||
...
|
||||
def _lvm_info_with_retry(self, cmd: str, info_type: Literal["lv"]) -> LvmVolumeInfo | None: ...
|
||||
|
||||
@overload
|
||||
def _lvm_info_with_retry(self, cmd: str, info_type: Literal['vg']) -> LvmGroupInfo | None:
|
||||
...
|
||||
def _lvm_info_with_retry(self, cmd: str, info_type: Literal["vg"]) -> LvmGroupInfo | None: ...
|
||||
|
||||
@overload
|
||||
def _lvm_info_with_retry(self, cmd: str, info_type: Literal['pvseg']) -> LvmPVInfo | None:
|
||||
...
|
||||
def _lvm_info_with_retry(self, cmd: str, info_type: Literal["pvseg"]) -> LvmPVInfo | None: ...
|
||||
|
||||
def _lvm_info_with_retry(
|
||||
self,
|
||||
cmd: str,
|
||||
info_type: Literal['lv', 'vg', 'pvseg']
|
||||
info_type: Literal["lv", "vg", "pvseg"],
|
||||
) -> LvmVolumeInfo | LvmGroupInfo | LvmPVInfo | None:
|
||||
while True:
|
||||
try:
|
||||
|
|
@ -430,77 +424,63 @@ class DeviceHandler:
|
|||
time.sleep(3)
|
||||
|
||||
def lvm_vol_info(self, lv_name: str) -> LvmVolumeInfo | None:
|
||||
cmd = (
|
||||
'lvs --reportformat json '
|
||||
'--unit B '
|
||||
f'-S lv_name={lv_name}'
|
||||
)
|
||||
cmd = f"lvs --reportformat json --unit B -S lv_name={lv_name}"
|
||||
|
||||
return self._lvm_info_with_retry(cmd, 'lv')
|
||||
return self._lvm_info_with_retry(cmd, "lv")
|
||||
|
||||
def lvm_group_info(self, vg_name: str) -> LvmGroupInfo | None:
|
||||
cmd = (
|
||||
'vgs --reportformat json '
|
||||
'--unit B '
|
||||
'-o vg_name,vg_uuid,vg_size '
|
||||
f'-S vg_name={vg_name}'
|
||||
)
|
||||
cmd = f"vgs --reportformat json --unit B -o vg_name,vg_uuid,vg_size -S vg_name={vg_name}"
|
||||
|
||||
return self._lvm_info_with_retry(cmd, 'vg')
|
||||
return self._lvm_info_with_retry(cmd, "vg")
|
||||
|
||||
def lvm_pvseg_info(self, vg_name: str, lv_name: str) -> LvmPVInfo | None:
|
||||
cmd = (
|
||||
'pvs '
|
||||
'--segments -o+lv_name,vg_name '
|
||||
f'-S vg_name={vg_name},lv_name={lv_name} '
|
||||
'--reportformat json '
|
||||
)
|
||||
cmd = f"pvs --segments -o+lv_name,vg_name -S vg_name={vg_name},lv_name={lv_name} --reportformat json "
|
||||
|
||||
return self._lvm_info_with_retry(cmd, 'pvseg')
|
||||
return self._lvm_info_with_retry(cmd, "pvseg")
|
||||
|
||||
def lvm_vol_change(self, vol: LvmVolume, activate: bool) -> None:
|
||||
active_flag = 'y' if activate else 'n'
|
||||
cmd = f'lvchange -a {active_flag} {vol.safe_dev_path}'
|
||||
active_flag = "y" if activate else "n"
|
||||
cmd = f"lvchange -a {active_flag} {vol.safe_dev_path}"
|
||||
|
||||
debug(f'lvchange volume: {cmd}')
|
||||
debug(f"lvchange volume: {cmd}")
|
||||
SysCommand(cmd)
|
||||
|
||||
def lvm_export_vg(self, vg: LvmVolumeGroup) -> None:
|
||||
cmd = f'vgexport {vg.name}'
|
||||
cmd = f"vgexport {vg.name}"
|
||||
|
||||
debug(f'vgexport: {cmd}')
|
||||
debug(f"vgexport: {cmd}")
|
||||
SysCommand(cmd)
|
||||
|
||||
def lvm_import_vg(self, vg: LvmVolumeGroup) -> None:
|
||||
cmd = f'vgimport {vg.name}'
|
||||
cmd = f"vgimport {vg.name}"
|
||||
|
||||
debug(f'vgimport: {cmd}')
|
||||
debug(f"vgimport: {cmd}")
|
||||
SysCommand(cmd)
|
||||
|
||||
def lvm_vol_reduce(self, vol_path: Path, amount: Size) -> None:
|
||||
val = amount.format_size(Unit.B, include_unit=False)
|
||||
cmd = f'lvreduce -L -{val}B {vol_path}'
|
||||
cmd = f"lvreduce -L -{val}B {vol_path}"
|
||||
|
||||
debug(f'Reducing LVM volume size: {cmd}')
|
||||
debug(f"Reducing LVM volume size: {cmd}")
|
||||
SysCommand(cmd)
|
||||
|
||||
def lvm_pv_create(self, pvs: Iterable[Path]) -> None:
|
||||
cmd = 'pvcreate ' + ' '.join([str(pv) for pv in pvs])
|
||||
debug(f'Creating LVM PVS: {cmd}')
|
||||
cmd = "pvcreate " + " ".join([str(pv) for pv in pvs])
|
||||
debug(f"Creating LVM PVS: {cmd}")
|
||||
|
||||
worker = SysCommandWorker(cmd)
|
||||
worker.poll()
|
||||
worker.write(b'y\n', line_ending=False)
|
||||
worker.write(b"y\n", line_ending=False)
|
||||
|
||||
def lvm_vg_create(self, pvs: Iterable[Path], vg_name: str) -> None:
|
||||
pvs_str = ' '.join([str(pv) for pv in pvs])
|
||||
cmd = f'vgcreate --yes {vg_name} {pvs_str}'
|
||||
pvs_str = " ".join([str(pv) for pv in pvs])
|
||||
cmd = f"vgcreate --yes {vg_name} {pvs_str}"
|
||||
|
||||
debug(f'Creating LVM group: {cmd}')
|
||||
debug(f"Creating LVM group: {cmd}")
|
||||
|
||||
worker = SysCommandWorker(cmd)
|
||||
worker.poll()
|
||||
worker.write(b'y\n', line_ending=False)
|
||||
worker.write(b"y\n", line_ending=False)
|
||||
|
||||
def lvm_vol_create(self, vg_name: str, volume: LvmVolume, offset: Size | None = None) -> None:
|
||||
if offset is not None:
|
||||
|
|
@ -509,32 +489,32 @@ class DeviceHandler:
|
|||
length = volume.length
|
||||
|
||||
length_str = length.format_size(Unit.B, include_unit=False)
|
||||
cmd = f'lvcreate --yes -L {length_str}B {vg_name} -n {volume.name}'
|
||||
cmd = f"lvcreate --yes -L {length_str}B {vg_name} -n {volume.name}"
|
||||
|
||||
debug(f'Creating volume: {cmd}')
|
||||
debug(f"Creating volume: {cmd}")
|
||||
|
||||
worker = SysCommandWorker(cmd)
|
||||
worker.poll()
|
||||
worker.write(b'y\n', line_ending=False)
|
||||
worker.write(b"y\n", line_ending=False)
|
||||
|
||||
volume.vg_name = vg_name
|
||||
volume.dev_path = Path(f'/dev/{vg_name}/{volume.name}')
|
||||
volume.dev_path = Path(f"/dev/{vg_name}/{volume.name}")
|
||||
|
||||
def _setup_partition(
|
||||
self,
|
||||
part_mod: PartitionModification,
|
||||
block_device: BDevice,
|
||||
disk: Disk,
|
||||
requires_delete: bool
|
||||
requires_delete: bool,
|
||||
) -> None:
|
||||
# when we require a delete and the partition to be (re)created
|
||||
# already exists then we have to delete it first
|
||||
if requires_delete and part_mod.status in [ModificationStatus.Modify, ModificationStatus.Delete]:
|
||||
info(f'Delete existing partition: {part_mod.safe_dev_path}')
|
||||
info(f"Delete existing partition: {part_mod.safe_dev_path}")
|
||||
part_info = self.find_partition(part_mod.safe_dev_path)
|
||||
|
||||
if not part_info:
|
||||
raise DiskError(f'No partition for dev path found: {part_mod.safe_dev_path}')
|
||||
raise DiskError(f"No partition for dev path found: {part_mod.safe_dev_path}")
|
||||
|
||||
disk.deletePartition(part_info.partition)
|
||||
|
||||
|
|
@ -543,18 +523,18 @@ class DeviceHandler:
|
|||
|
||||
start_sector = part_mod.start.convert(
|
||||
Unit.sectors,
|
||||
block_device.device_info.sector_size
|
||||
block_device.device_info.sector_size,
|
||||
)
|
||||
|
||||
length_sector = part_mod.length.convert(
|
||||
Unit.sectors,
|
||||
block_device.device_info.sector_size
|
||||
block_device.device_info.sector_size,
|
||||
)
|
||||
|
||||
geometry = Geometry(
|
||||
device=block_device.disk.device,
|
||||
start=start_sector.value,
|
||||
length=length_sector.value
|
||||
length=length_sector.value,
|
||||
)
|
||||
|
||||
fs_value = part_mod.safe_fs_type.parted_value
|
||||
|
|
@ -564,20 +544,20 @@ class DeviceHandler:
|
|||
disk=disk,
|
||||
type=part_mod.type.get_partition_code(),
|
||||
fs=filesystem,
|
||||
geometry=geometry
|
||||
geometry=geometry,
|
||||
)
|
||||
|
||||
for flag in part_mod.flags:
|
||||
partition.setFlag(flag.flag_id)
|
||||
|
||||
debug(f'\tType: {part_mod.type.value}')
|
||||
debug(f'\tFilesystem: {fs_value}')
|
||||
debug(f'\tGeometry: {start_sector.value} start sector, {length_sector.value} length')
|
||||
debug(f"\tType: {part_mod.type.value}")
|
||||
debug(f"\tFilesystem: {fs_value}")
|
||||
debug(f"\tGeometry: {start_sector.value} start sector, {length_sector.value} length")
|
||||
|
||||
try:
|
||||
disk.addPartition(partition=partition, constraint=disk.device.optimalAlignedConstraint)
|
||||
except PartitionException as ex:
|
||||
raise DiskError(f'Unable to add partition, most likely due to overlapping sectors: {ex}') from ex
|
||||
raise DiskError(f"Unable to add partition, most likely due to overlapping sectors: {ex}") from ex
|
||||
|
||||
if disk.type == PartitionTable.GPT.value:
|
||||
if part_mod.is_root():
|
||||
|
|
@ -592,18 +572,18 @@ class DeviceHandler:
|
|||
lsblk_info = get_lsblk_info(path)
|
||||
|
||||
if not lsblk_info.partn:
|
||||
debug(f'Unable to determine new partition number: {path}\n{lsblk_info}')
|
||||
raise DiskError(f'Unable to determine new partition number: {path}')
|
||||
debug(f"Unable to determine new partition number: {path}\n{lsblk_info}")
|
||||
raise DiskError(f"Unable to determine new partition number: {path}")
|
||||
|
||||
if not lsblk_info.partuuid:
|
||||
debug(f'Unable to determine new partition uuid: {path}\n{lsblk_info}')
|
||||
raise DiskError(f'Unable to determine new partition uuid: {path}')
|
||||
debug(f"Unable to determine new partition uuid: {path}\n{lsblk_info}")
|
||||
raise DiskError(f"Unable to determine new partition uuid: {path}")
|
||||
|
||||
if not lsblk_info.uuid:
|
||||
debug(f'Unable to determine new uuid: {path}\n{lsblk_info}')
|
||||
raise DiskError(f'Unable to determine new uuid: {path}')
|
||||
debug(f"Unable to determine new uuid: {path}\n{lsblk_info}")
|
||||
raise DiskError(f"Unable to determine new uuid: {path}")
|
||||
|
||||
debug(f'partition information found: {lsblk_info.model_dump_json()}')
|
||||
debug(f"partition information found: {lsblk_info.model_dump_json()}")
|
||||
|
||||
return lsblk_info
|
||||
|
||||
|
|
@ -611,14 +591,14 @@ class DeviceHandler:
|
|||
self,
|
||||
path: Path,
|
||||
btrfs_subvols: list[SubvolumeModification],
|
||||
mount_options: list[str]
|
||||
mount_options: list[str],
|
||||
) -> None:
|
||||
info(f'Creating subvolumes: {path}')
|
||||
info(f"Creating subvolumes: {path}")
|
||||
|
||||
self.mount(path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True)
|
||||
|
||||
for sub_vol in sorted(btrfs_subvols, key=lambda x: x.name):
|
||||
debug(f'Creating subvolume: {sub_vol.name}')
|
||||
debug(f"Creating subvolume: {sub_vol.name}")
|
||||
|
||||
subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name
|
||||
|
||||
|
|
@ -626,38 +606,38 @@ class DeviceHandler:
|
|||
|
||||
if BtrfsMountOption.nodatacow.value in mount_options:
|
||||
try:
|
||||
SysCommand(f'chattr +C {subvol_path}')
|
||||
SysCommand(f"chattr +C {subvol_path}")
|
||||
except SysCallError as err:
|
||||
raise DiskError(f'Could not set nodatacow attribute at {subvol_path}: {err}')
|
||||
raise DiskError(f"Could not set nodatacow attribute at {subvol_path}: {err}")
|
||||
|
||||
if BtrfsMountOption.compress.value in mount_options:
|
||||
try:
|
||||
SysCommand(f'chattr +c {subvol_path}')
|
||||
SysCommand(f"chattr +c {subvol_path}")
|
||||
except SysCallError as err:
|
||||
raise DiskError(f'Could not set compress attribute at {subvol_path}: {err}')
|
||||
raise DiskError(f"Could not set compress attribute at {subvol_path}: {err}")
|
||||
|
||||
umount(path)
|
||||
|
||||
def create_btrfs_volumes(
|
||||
self,
|
||||
part_mod: PartitionModification,
|
||||
enc_conf: DiskEncryption | None = None
|
||||
enc_conf: DiskEncryption | None = None,
|
||||
) -> None:
|
||||
info(f'Creating subvolumes: {part_mod.safe_dev_path}')
|
||||
info(f"Creating subvolumes: {part_mod.safe_dev_path}")
|
||||
|
||||
# unlock the partition first if it's encrypted
|
||||
if enc_conf is not None and part_mod in enc_conf.partitions:
|
||||
if not part_mod.mapper_name:
|
||||
raise ValueError('No device path specified for modification')
|
||||
raise ValueError("No device path specified for modification")
|
||||
|
||||
luks_handler = self.unlock_luks2_dev(
|
||||
part_mod.safe_dev_path,
|
||||
part_mod.mapper_name,
|
||||
enc_conf.encryption_password
|
||||
enc_conf.encryption_password,
|
||||
)
|
||||
|
||||
if not luks_handler.mapper_dev:
|
||||
raise DiskError('Failed to unlock luks device')
|
||||
raise DiskError("Failed to unlock luks device")
|
||||
|
||||
dev_path = luks_handler.mapper_dev
|
||||
else:
|
||||
|
|
@ -668,11 +648,11 @@ class DeviceHandler:
|
|||
dev_path,
|
||||
self._TMP_BTRFS_MOUNT,
|
||||
create_target_mountpoint=True,
|
||||
options=part_mod.mount_options
|
||||
options=part_mod.mount_options,
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -687,7 +667,7 @@ class DeviceHandler:
|
|||
self,
|
||||
dev_path: Path,
|
||||
mapper_name: str,
|
||||
enc_password: Password | None
|
||||
enc_password: Password | None,
|
||||
) -> Luks2:
|
||||
luks_handler = Luks2(dev_path, mapper_name=mapper_name, password=enc_password)
|
||||
|
||||
|
|
@ -695,17 +675,17 @@ class DeviceHandler:
|
|||
luks_handler.unlock()
|
||||
|
||||
if not luks_handler.is_unlocked():
|
||||
raise DiskError(f'Failed to unlock luks2 device: {dev_path}')
|
||||
raise DiskError(f"Failed to unlock luks2 device: {dev_path}")
|
||||
|
||||
return luks_handler
|
||||
|
||||
def umount_all_existing(self, device_path: Path) -> None:
|
||||
debug(f'Unmounting all existing partitions: {device_path}')
|
||||
debug(f"Unmounting all existing partitions: {device_path}")
|
||||
|
||||
existing_partitions = self._devices[device_path].partition_infos
|
||||
|
||||
for partition in existing_partitions:
|
||||
debug(f'Unmounting: {partition.path}')
|
||||
debug(f"Unmounting: {partition.path}")
|
||||
|
||||
# un-mount for existing encrypted partitions
|
||||
if partition.fs_type == FilesystemType.Crypto_luks:
|
||||
|
|
@ -716,7 +696,7 @@ class DeviceHandler:
|
|||
def partition(
|
||||
self,
|
||||
modification: DeviceModification,
|
||||
partition_table: PartitionTable | None = None
|
||||
partition_table: PartitionTable | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Create a partition table on the block device and create all partitions.
|
||||
|
|
@ -726,15 +706,15 @@ class DeviceHandler:
|
|||
# WARNING: the entire device will be wiped and all data lost
|
||||
if modification.wipe:
|
||||
if partition_table.is_mbr() and len(modification.partitions) > 3:
|
||||
raise DiskError('Too many partitions on disk, MBR disks can only have 3 primary partitions')
|
||||
raise DiskError("Too many partitions on disk, MBR disks can only have 3 primary partitions")
|
||||
|
||||
self.wipe_dev(modification.device)
|
||||
disk = freshDisk(modification.device.disk.device, partition_table.value)
|
||||
else:
|
||||
info(f'Use existing device: {modification.device_path}')
|
||||
info(f"Use existing device: {modification.device_path}")
|
||||
disk = modification.device.disk
|
||||
|
||||
info(f'Creating partitions: {modification.device_path}')
|
||||
info(f"Creating partitions: {modification.device_path}")
|
||||
|
||||
# don't touch existing partitions
|
||||
filtered_part = [p for p in modification.partitions if not p.exists()]
|
||||
|
|
@ -750,9 +730,9 @@ class DeviceHandler:
|
|||
@staticmethod
|
||||
def swapon(path: Path) -> None:
|
||||
try:
|
||||
SysCommand(['swapon', str(path)])
|
||||
SysCommand(["swapon", str(path)])
|
||||
except SysCallError as err:
|
||||
raise DiskError(f'Could not enable swap {path}:\n{err.message}')
|
||||
raise DiskError(f"Could not enable swap {path}:\n{err.message}")
|
||||
|
||||
def mount(
|
||||
self,
|
||||
|
|
@ -760,36 +740,36 @@ class DeviceHandler:
|
|||
target_mountpoint: Path,
|
||||
mount_fs: str | None = None,
|
||||
create_target_mountpoint: bool = True,
|
||||
options: list[str] = []
|
||||
options: list[str] = [],
|
||||
) -> None:
|
||||
if create_target_mountpoint and not target_mountpoint.exists():
|
||||
target_mountpoint.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not target_mountpoint.exists():
|
||||
raise ValueError('Target mountpoint does not exist')
|
||||
raise ValueError("Target mountpoint does not exist")
|
||||
|
||||
lsblk_info = get_lsblk_info(dev_path)
|
||||
if target_mountpoint in lsblk_info.mountpoints:
|
||||
info(f'Device already mounted at {target_mountpoint}')
|
||||
info(f"Device already mounted at {target_mountpoint}")
|
||||
return
|
||||
|
||||
cmd = ['mount']
|
||||
cmd = ["mount"]
|
||||
|
||||
if len(options):
|
||||
cmd.extend(('-o', ','.join(options)))
|
||||
cmd.extend(("-o", ",".join(options)))
|
||||
if mount_fs:
|
||||
cmd.extend(('-t', mount_fs))
|
||||
cmd.extend(("-t", mount_fs))
|
||||
|
||||
cmd.extend((str(dev_path), str(target_mountpoint)))
|
||||
|
||||
command = ' '.join(cmd)
|
||||
command = " ".join(cmd)
|
||||
|
||||
debug(f'Mounting {dev_path}: {command}')
|
||||
debug(f"Mounting {dev_path}: {command}")
|
||||
|
||||
try:
|
||||
SysCommand(command)
|
||||
except SysCallError as err:
|
||||
raise DiskError(f'Could not mount {dev_path}: {command}\n{err.message}')
|
||||
raise DiskError(f"Could not mount {dev_path}: {command}\n{err.message}")
|
||||
|
||||
def detect_pre_mounted_mods(self, base_mountpoint: Path) -> list[DeviceModification]:
|
||||
part_mods: dict[Path, list[PartitionModification]] = {}
|
||||
|
|
@ -819,15 +799,15 @@ class DeviceHandler:
|
|||
|
||||
def partprobe(self, path: Path | None = None) -> None:
|
||||
if path is not None:
|
||||
command = f'partprobe {path}'
|
||||
command = f"partprobe {path}"
|
||||
else:
|
||||
command = 'partprobe'
|
||||
command = "partprobe"
|
||||
|
||||
try:
|
||||
debug(f'Calling partprobe: {command}')
|
||||
debug(f"Calling partprobe: {command}")
|
||||
SysCommand(command)
|
||||
except SysCallError as err:
|
||||
if 'have been written, but we have been unable to inform the kernel of the change' in str(err):
|
||||
if "have been written, but we have been unable to inform the kernel of the change" in str(err):
|
||||
log(f"Partprobe was not able to inform the kernel of the new disk state (ignoring error): {err}", fg="gray", level=logging.INFO)
|
||||
else:
|
||||
error(f'"{command}" failed to run (continuing anyway): {err}')
|
||||
|
|
@ -838,7 +818,7 @@ class DeviceHandler:
|
|||
@param dev_path: Device path of the partition to be wiped.
|
||||
@type dev_path: str
|
||||
"""
|
||||
with open(dev_path, 'wb') as p:
|
||||
with open(dev_path, "wb") as p:
|
||||
p.write(bytearray(1024))
|
||||
|
||||
def wipe_dev(self, block_device: BDevice) -> None:
|
||||
|
|
@ -847,7 +827,7 @@ class DeviceHandler:
|
|||
This is not intended to be secure, but rather to ensure that
|
||||
auto-discovery tools don't recognize anything here.
|
||||
"""
|
||||
info(f'Wiping partitions and metadata: {block_device.device_info.path}')
|
||||
info(f"Wiping partitions and metadata: {block_device.device_info.path}")
|
||||
|
||||
for partition in block_device.partition_infos:
|
||||
luks = Luks2(partition.path)
|
||||
|
|
@ -861,9 +841,9 @@ class DeviceHandler:
|
|||
@staticmethod
|
||||
def udev_sync() -> None:
|
||||
try:
|
||||
SysCommand('udevadm settle')
|
||||
SysCommand("udevadm settle")
|
||||
except SysCallError as err:
|
||||
debug(f'Failed to synchronize with udev: {err}')
|
||||
debug(f"Failed to synchronize with udev: {err}")
|
||||
|
||||
|
||||
device_handler = DeviceHandler()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
|||
else:
|
||||
self._disk_menu_config = DiskMenuConfig(
|
||||
disk_config=disk_layout_config,
|
||||
lvm_config=disk_layout_config.lvm_config
|
||||
lvm_config=disk_layout_config.lvm_config,
|
||||
)
|
||||
|
||||
menu_optioons = self._define_menu_options()
|
||||
|
|
@ -38,25 +38,25 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
|||
super().__init__(
|
||||
self._item_group,
|
||||
self._disk_menu_config,
|
||||
allow_reset=True
|
||||
allow_reset=True,
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=str(_('Partitioning')),
|
||||
text=str(_("Partitioning")),
|
||||
action=self._select_disk_layout_config,
|
||||
value=self._disk_menu_config.disk_config,
|
||||
preview_action=self._prev_disk_layouts,
|
||||
key='disk_config'
|
||||
key="disk_config",
|
||||
),
|
||||
MenuItem(
|
||||
text='LVM (BETA)',
|
||||
text="LVM (BETA)",
|
||||
action=self._select_lvm_config,
|
||||
value=self._disk_menu_config.lvm_config,
|
||||
preview_action=self._prev_lvm_config,
|
||||
dependencies=[self._check_dep_lvm],
|
||||
key='lvm_config'
|
||||
key="lvm_config",
|
||||
),
|
||||
]
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
|||
return None
|
||||
|
||||
def _check_dep_lvm(self) -> bool:
|
||||
disk_layout_conf: DiskLayoutConfiguration | None = self._menu_item_group.find_by_key('disk_config').value
|
||||
disk_layout_conf: DiskLayoutConfiguration | None = self._menu_item_group.find_by_key("disk_config").value
|
||||
|
||||
if disk_layout_conf and disk_layout_conf.config_type == DiskLayoutType.Default:
|
||||
return True
|
||||
|
|
@ -80,17 +80,17 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
|||
|
||||
def _select_disk_layout_config(
|
||||
self,
|
||||
preset: DiskLayoutConfiguration | None
|
||||
preset: DiskLayoutConfiguration | None,
|
||||
) -> DiskLayoutConfiguration | None:
|
||||
disk_config = select_disk_config(preset)
|
||||
|
||||
if disk_config != preset:
|
||||
self._menu_item_group.find_by_key('lvm_config').value = None
|
||||
self._menu_item_group.find_by_key("lvm_config").value = None
|
||||
|
||||
return disk_config
|
||||
|
||||
def _select_lvm_config(self, preset: LvmConfiguration | None) -> LvmConfiguration | None:
|
||||
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
|
||||
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key("disk_config").value
|
||||
|
||||
if disk_config:
|
||||
return select_lvm_config(disk_config, preset=preset)
|
||||
|
|
@ -104,28 +104,28 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
|||
disk_layout_conf = item.get_value()
|
||||
|
||||
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
|
||||
msg = str(_('Configuration type: {}')).format(disk_layout_conf.config_type.display_msg()) + '\n'
|
||||
msg += str(_('Mountpoint')) + ': ' + str(disk_layout_conf.mountpoint)
|
||||
msg = str(_("Configuration type: {}")).format(disk_layout_conf.config_type.display_msg()) + "\n"
|
||||
msg += str(_("Mountpoint")) + ": " + str(disk_layout_conf.mountpoint)
|
||||
return msg
|
||||
|
||||
device_mods = [d for d in disk_layout_conf.device_modifications if d.partitions]
|
||||
|
||||
if device_mods:
|
||||
output_partition = '{}: {}\n'.format(str(_('Configuration')), disk_layout_conf.config_type.display_msg())
|
||||
output_btrfs = ''
|
||||
output_partition = "{}: {}\n".format(str(_("Configuration")), disk_layout_conf.config_type.display_msg())
|
||||
output_btrfs = ""
|
||||
|
||||
for mod in device_mods:
|
||||
# create partition table
|
||||
partition_table = FormattedOutput.as_table(mod.partitions)
|
||||
|
||||
output_partition += f'{mod.device_path}: {mod.device.device_info.model}\n'
|
||||
output_partition += '{}: {}\n'.format(str(_('Wipe')), mod.wipe)
|
||||
output_partition += partition_table + '\n'
|
||||
output_partition += f"{mod.device_path}: {mod.device.device_info.model}\n"
|
||||
output_partition += "{}: {}\n".format(str(_("Wipe")), mod.wipe)
|
||||
output_partition += partition_table + "\n"
|
||||
|
||||
# create btrfs table
|
||||
btrfs_partitions = [p for p in mod.partitions if p.btrfs_subvols]
|
||||
for partition in btrfs_partitions:
|
||||
output_btrfs += FormattedOutput.as_table(partition.btrfs_subvols) + '\n'
|
||||
output_btrfs += FormattedOutput.as_table(partition.btrfs_subvols) + "\n"
|
||||
|
||||
output = output_partition + output_btrfs
|
||||
return output.rstrip()
|
||||
|
|
@ -138,16 +138,16 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
|
|||
|
||||
lvm_config: LvmConfiguration = item.value
|
||||
|
||||
output = '{}: {}\n'.format(str(_('Configuration')), lvm_config.config_type.display_msg())
|
||||
output = "{}: {}\n".format(str(_("Configuration")), lvm_config.config_type.display_msg())
|
||||
|
||||
for vol_gp in lvm_config.vol_groups:
|
||||
pv_table = FormattedOutput.as_table(vol_gp.pvs)
|
||||
output += '{}:\n{}'.format(str(_('Physical volumes')), pv_table)
|
||||
output += "{}:\n{}".format(str(_("Physical volumes")), pv_table)
|
||||
|
||||
output += f'\nVolume Group: {vol_gp.name}'
|
||||
output += f"\nVolume Group: {vol_gp.name}"
|
||||
|
||||
lvm_volumes = FormattedOutput.as_table(vol_gp.volumes)
|
||||
output += '\n\n{}:\n{}'.format(str(_('Volumes')), lvm_volumes)
|
||||
output += "\n\n{}:\n{}".format(str(_("Volumes")), lvm_volumes)
|
||||
|
||||
return output
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
|||
def __init__(
|
||||
self,
|
||||
disk_config: DiskLayoutConfiguration,
|
||||
preset: DiskEncryption | None = None
|
||||
preset: DiskEncryption | None = None,
|
||||
):
|
||||
if preset:
|
||||
self._enc_config = preset
|
||||
|
|
@ -50,49 +50,49 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
|||
super().__init__(
|
||||
self._item_group,
|
||||
self._enc_config,
|
||||
allow_reset=True
|
||||
allow_reset=True,
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=str(_('Encryption type')),
|
||||
text=str(_("Encryption type")),
|
||||
action=lambda x: select_encryption_type(self._disk_config, x),
|
||||
value=self._enc_config.encryption_type,
|
||||
preview_action=self._preview,
|
||||
key='encryption_type'
|
||||
key="encryption_type",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Encryption password')),
|
||||
text=str(_("Encryption password")),
|
||||
action=lambda x: select_encrypted_password(),
|
||||
value=self._enc_config.encryption_password,
|
||||
dependencies=[self._check_dep_enc_type],
|
||||
preview_action=self._preview,
|
||||
key='encryption_password'
|
||||
key="encryption_password",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Partitions')),
|
||||
text=str(_("Partitions")),
|
||||
action=lambda x: select_partitions_to_encrypt(self._disk_config.device_modifications, x),
|
||||
value=self._enc_config.partitions,
|
||||
dependencies=[self._check_dep_partitions],
|
||||
preview_action=self._preview,
|
||||
key='partitions'
|
||||
key="partitions",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('LVM volumes')),
|
||||
text=str(_("LVM volumes")),
|
||||
action=self._select_lvm_vols,
|
||||
value=self._enc_config.lvm_volumes,
|
||||
dependencies=[self._check_dep_lvm_vols],
|
||||
preview_action=self._preview,
|
||||
key='lvm_volumes'
|
||||
key="lvm_volumes",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('HSM')),
|
||||
text=str(_("HSM")),
|
||||
action=select_hsm,
|
||||
value=self._enc_config.hsm_device,
|
||||
dependencies=[self._check_dep_enc_type],
|
||||
preview_action=self._preview,
|
||||
key='hsm_device'
|
||||
key="hsm_device",
|
||||
),
|
||||
]
|
||||
|
||||
|
|
@ -102,19 +102,19 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
|||
return []
|
||||
|
||||
def _check_dep_enc_type(self) -> bool:
|
||||
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
|
||||
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value
|
||||
if enc_type and enc_type != EncryptionType.NoEncryption:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _check_dep_partitions(self) -> bool:
|
||||
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
|
||||
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value
|
||||
if enc_type and enc_type in [EncryptionType.Luks, EncryptionType.LvmOnLuks]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _check_dep_lvm_vols(self) -> bool:
|
||||
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
|
||||
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value
|
||||
if enc_type and enc_type == EncryptionType.LuksOnLvm:
|
||||
return True
|
||||
return False
|
||||
|
|
@ -123,10 +123,10 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
|||
def run(self) -> DiskEncryption | None:
|
||||
super().run()
|
||||
|
||||
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
|
||||
enc_password: Password | None = self._item_group.find_by_key('encryption_password').value
|
||||
enc_partitions = self._item_group.find_by_key('partitions').value
|
||||
enc_lvm_vols = self._item_group.find_by_key('lvm_volumes').value
|
||||
enc_type: EncryptionType | None = self._item_group.find_by_key("encryption_type").value
|
||||
enc_password: Password | None = self._item_group.find_by_key("encryption_password").value
|
||||
enc_partitions = self._item_group.find_by_key("partitions").value
|
||||
enc_lvm_vols = self._item_group.find_by_key("lvm_volumes").value
|
||||
|
||||
assert enc_type is not None
|
||||
assert enc_partitions is not None
|
||||
|
|
@ -144,28 +144,28 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
|||
encryption_type=enc_type,
|
||||
partitions=enc_partitions,
|
||||
lvm_volumes=enc_lvm_vols,
|
||||
hsm_device=self._enc_config.hsm_device
|
||||
hsm_device=self._enc_config.hsm_device,
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def _preview(self, item: MenuItem) -> str | None:
|
||||
output = ''
|
||||
output = ""
|
||||
|
||||
if (enc_type := self._prev_type()) is not None:
|
||||
output += enc_type
|
||||
|
||||
if (enc_pwd := self._prev_password()) is not None:
|
||||
output += f'\n{enc_pwd}'
|
||||
output += f"\n{enc_pwd}"
|
||||
|
||||
if (fido_device := self._prev_hsm()) is not None:
|
||||
output += f'\n{fido_device}'
|
||||
output += f"\n{fido_device}"
|
||||
|
||||
if (partitions := self._prev_partitions()) is not None:
|
||||
output += f'\n\n{partitions}'
|
||||
output += f"\n\n{partitions}"
|
||||
|
||||
if (lvm := self._prev_lvm_vols()) is not None:
|
||||
output += f'\n\n{lvm}'
|
||||
output += f"\n\n{lvm}"
|
||||
|
||||
if not output:
|
||||
return None
|
||||
|
|
@ -173,51 +173,51 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
|||
return output
|
||||
|
||||
def _prev_type(self) -> str | None:
|
||||
enc_type = self._item_group.find_by_key('encryption_type').value
|
||||
enc_type = self._item_group.find_by_key("encryption_type").value
|
||||
|
||||
if enc_type:
|
||||
enc_text = EncryptionType.type_to_text(enc_type)
|
||||
return f'{_("Encryption type")}: {enc_text}'
|
||||
return f"{_('Encryption type')}: {enc_text}"
|
||||
|
||||
return None
|
||||
|
||||
def _prev_password(self) -> str | None:
|
||||
enc_pwd = self._item_group.find_by_key('encryption_password').value
|
||||
enc_pwd = self._item_group.find_by_key("encryption_password").value
|
||||
|
||||
if enc_pwd:
|
||||
return f'{_("Encryption password")}: {enc_pwd.hidden()}'
|
||||
return f"{_('Encryption password')}: {enc_pwd.hidden()}"
|
||||
|
||||
return None
|
||||
|
||||
def _prev_partitions(self) -> str | None:
|
||||
partitions: list[PartitionModification] | None = self._item_group.find_by_key('partitions').value
|
||||
partitions: list[PartitionModification] | None = self._item_group.find_by_key("partitions").value
|
||||
|
||||
if partitions:
|
||||
output = str(_('Partitions to be encrypted')) + '\n'
|
||||
output = str(_("Partitions to be encrypted")) + "\n"
|
||||
output += FormattedOutput.as_table(partitions)
|
||||
return output.rstrip()
|
||||
|
||||
return None
|
||||
|
||||
def _prev_lvm_vols(self) -> str | None:
|
||||
volumes: list[PartitionModification] | None = self._item_group.find_by_key('lvm_volumes').value
|
||||
volumes: list[PartitionModification] | None = self._item_group.find_by_key("lvm_volumes").value
|
||||
|
||||
if volumes:
|
||||
output = str(_('LVM volumes to be encrypted')) + '\n'
|
||||
output = str(_("LVM volumes to be encrypted")) + "\n"
|
||||
output += FormattedOutput.as_table(volumes)
|
||||
return output.rstrip()
|
||||
|
||||
return None
|
||||
|
||||
def _prev_hsm(self) -> str | None:
|
||||
fido_device: Fido2Device | None = self._item_group.find_by_key('hsm_device').value
|
||||
fido_device: Fido2Device | None = self._item_group.find_by_key("hsm_device").value
|
||||
|
||||
if not fido_device:
|
||||
return None
|
||||
|
||||
output = str(fido_device.path)
|
||||
output += f' ({fido_device.manufacturer}, {fido_device.product})'
|
||||
return f'{_("HSM device")}: {output}'
|
||||
output += f" ({fido_device.manufacturer}, {fido_device.product})"
|
||||
return f"{_('HSM device')}: {output}"
|
||||
|
||||
|
||||
def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: EncryptionType) -> EncryptionType | None:
|
||||
|
|
@ -238,7 +238,7 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt
|
|||
allow_skip=True,
|
||||
allow_reset=True,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Encryption type')))
|
||||
frame=FrameProperties.min(str(_("Encryption type"))),
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -251,18 +251,18 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt
|
|||
|
||||
|
||||
def select_encrypted_password() -> Password | None:
|
||||
header = str(_('Enter disk encryption password (leave blank for no encryption)')) + '\n'
|
||||
header = str(_("Enter disk encryption password (leave blank for no encryption)")) + "\n"
|
||||
password = get_password(
|
||||
text=str(_('Disk encryption password')),
|
||||
text=str(_("Disk encryption password")),
|
||||
header=header,
|
||||
allow_skip=True
|
||||
allow_skip=True,
|
||||
)
|
||||
|
||||
return password
|
||||
|
||||
|
||||
def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
|
||||
header = str(_('Select a FIDO2 device to use for HSM')) + '\n'
|
||||
header = str(_("Select a FIDO2 device to use for HSM")) + "\n"
|
||||
|
||||
try:
|
||||
fido_devices = Fido2.get_fido2_devices()
|
||||
|
|
@ -276,7 +276,7 @@ def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
|
|||
group,
|
||||
header=header,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -292,16 +292,13 @@ def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
|
|||
|
||||
def select_partitions_to_encrypt(
|
||||
modification: list[DeviceModification],
|
||||
preset: list[PartitionModification]
|
||||
preset: list[PartitionModification],
|
||||
) -> list[PartitionModification]:
|
||||
partitions: list[PartitionModification] = []
|
||||
|
||||
# do not allow encrypting the boot partition
|
||||
for mod in modification:
|
||||
partitions += [
|
||||
p for p in mod.partitions
|
||||
if p.mountpoint != Path('/boot') and not p.is_swap()
|
||||
]
|
||||
partitions += [p for p in mod.partitions if p.mountpoint != Path("/boot") and not p.is_swap()]
|
||||
|
||||
# do not allow encrypting existing partitions that are not marked as wipe
|
||||
avail_partitions = [p for p in partitions if not p.exists()]
|
||||
|
|
@ -314,7 +311,7 @@ def select_partitions_to_encrypt(
|
|||
group,
|
||||
alignment=Alignment.CENTER,
|
||||
multi=True,
|
||||
allow_skip=True
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -331,7 +328,7 @@ def select_partitions_to_encrypt(
|
|||
|
||||
def select_lvm_vols_to_encrypt(
|
||||
lvm_config: LvmConfiguration,
|
||||
preset: list[LvmVolume]
|
||||
preset: list[LvmVolume],
|
||||
) -> list[LvmVolume]:
|
||||
volumes: list[LvmVolume] = lvm_config.get_all_volumes()
|
||||
|
||||
|
|
@ -341,7 +338,7 @@ def select_lvm_vols_to_encrypt(
|
|||
result = SelectMenu[LvmVolume](
|
||||
group,
|
||||
alignment=Alignment.CENTER,
|
||||
multi=True
|
||||
multi=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ class Fido2:
|
|||
try:
|
||||
ret = SysCommand("systemd-cryptenroll --fido2-device=list").decode()
|
||||
except SysCallError:
|
||||
error('fido2 support is most likely not installed')
|
||||
raise ValueError('HSM devices can not be detected, is libfido2 installed?')
|
||||
error("fido2 support is most likely not installed")
|
||||
raise ValueError("HSM devices can not be detected, is libfido2 installed?")
|
||||
|
||||
fido_devices = clear_vt100_escape_codes_from_str(ret)
|
||||
|
||||
|
|
@ -51,10 +51,10 @@ class Fido2:
|
|||
product_pos = 0
|
||||
devices = []
|
||||
|
||||
for line in fido_devices.split('\r\n'):
|
||||
if '/dev' not in line:
|
||||
manufacturer_pos = line.find('MANUFACTURER')
|
||||
product_pos = line.find('PRODUCT')
|
||||
for line in fido_devices.split("\r\n"):
|
||||
if "/dev" not in line:
|
||||
manufacturer_pos = line.find("MANUFACTURER")
|
||||
product_pos = line.find("PRODUCT")
|
||||
continue
|
||||
|
||||
path = line[:manufacturer_pos].rstrip()
|
||||
|
|
@ -62,7 +62,7 @@ class Fido2:
|
|||
product = line[product_pos:]
|
||||
|
||||
devices.append(
|
||||
Fido2Device(Path(path), manufacturer, product)
|
||||
Fido2Device(Path(path), manufacturer, product),
|
||||
)
|
||||
|
||||
cls._loaded = True
|
||||
|
|
@ -75,7 +75,7 @@ class Fido2:
|
|||
cls,
|
||||
hsm_device: Fido2Device,
|
||||
dev_path: Path,
|
||||
password: Password
|
||||
password: Password,
|
||||
) -> None:
|
||||
worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device.path} {dev_path}", peek_output=True)
|
||||
pw_inputted = False
|
||||
|
|
@ -83,12 +83,12 @@ class Fido2:
|
|||
|
||||
while worker.is_alive():
|
||||
if pw_inputted is False:
|
||||
if bytes(f"please enter current passphrase for disk {dev_path}", 'UTF-8') in worker._trace_log.lower():
|
||||
worker.write(bytes(password.plaintext, 'UTF-8'))
|
||||
if bytes(f"please enter current passphrase for disk {dev_path}", "UTF-8") in worker._trace_log.lower():
|
||||
worker.write(bytes(password.plaintext, "UTF-8"))
|
||||
pw_inputted = True
|
||||
elif pin_inputted is False:
|
||||
if bytes("please enter security token pin", 'UTF-8') in worker._trace_log.lower():
|
||||
worker.write(bytes(getpass.getpass(" "), 'UTF-8'))
|
||||
if bytes("please enter security token pin", "UTF-8") in worker._trace_log.lower():
|
||||
worker.write(bytes(getpass.getpass(" "), "UTF-8"))
|
||||
pin_inputted = True
|
||||
|
||||
info('You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds')
|
||||
info("You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds")
|
||||
|
|
|
|||
|
|
@ -37,23 +37,23 @@ class FilesystemHandler:
|
|||
def __init__(
|
||||
self,
|
||||
disk_config: DiskLayoutConfiguration,
|
||||
enc_conf: DiskEncryption | None = None
|
||||
enc_conf: DiskEncryption | None = None,
|
||||
):
|
||||
self._disk_config = disk_config
|
||||
self._enc_config = enc_conf
|
||||
|
||||
def perform_filesystem_operations(self, show_countdown: bool = True) -> None:
|
||||
if self._disk_config.config_type == DiskLayoutType.Pre_mount:
|
||||
debug('Disk layout configuration is set to pre-mount, not performing any operations')
|
||||
debug("Disk layout configuration is set to pre-mount, not performing any operations")
|
||||
return
|
||||
|
||||
device_mods = [d for d in self._disk_config.device_modifications if d.partitions]
|
||||
|
||||
if not device_mods:
|
||||
debug('No modifications required')
|
||||
debug("No modifications required")
|
||||
return
|
||||
|
||||
device_paths = ', '.join([str(mod.device.device_info.path) for mod in device_mods])
|
||||
device_paths = ", ".join([str(mod.device.device_info.path) for mod in device_mods])
|
||||
|
||||
if show_countdown:
|
||||
self._final_warning(device_paths)
|
||||
|
|
@ -73,10 +73,10 @@ class FilesystemHandler:
|
|||
if self._disk_config.lvm_config:
|
||||
for mod in device_mods:
|
||||
if boot_part := mod.get_boot_partition():
|
||||
debug(f'Formatting boot partition: {boot_part.dev_path}')
|
||||
debug(f"Formatting boot partition: {boot_part.dev_path}")
|
||||
self._format_partitions(
|
||||
[boot_part],
|
||||
mod.device_path
|
||||
mod.device_path,
|
||||
)
|
||||
|
||||
self.perform_lvm_operations()
|
||||
|
|
@ -84,7 +84,7 @@ class FilesystemHandler:
|
|||
for mod in device_mods:
|
||||
self._format_partitions(
|
||||
mod.partitions,
|
||||
mod.device_path
|
||||
mod.device_path,
|
||||
)
|
||||
|
||||
for part_mod in mod.partitions:
|
||||
|
|
@ -94,7 +94,7 @@ class FilesystemHandler:
|
|||
def _format_partitions(
|
||||
self,
|
||||
partitions: list[PartitionModification],
|
||||
device_path: Path
|
||||
device_path: Path,
|
||||
) -> None:
|
||||
"""
|
||||
Format can be given an overriding path, for instance /dev/null to test
|
||||
|
|
@ -113,7 +113,7 @@ class FilesystemHandler:
|
|||
part_mod.safe_dev_path,
|
||||
part_mod.mapper_name,
|
||||
part_mod.safe_fs_type,
|
||||
self._enc_config
|
||||
self._enc_config,
|
||||
)
|
||||
else:
|
||||
device_handler.format(part_mod.safe_fs_type, part_mod.safe_dev_path)
|
||||
|
|
@ -130,12 +130,11 @@ class FilesystemHandler:
|
|||
def _validate_partitions(self, partitions: list[PartitionModification]) -> None:
|
||||
checks = {
|
||||
# verify that all partitions have a path set (which implies that they have been created)
|
||||
lambda x: x.dev_path is None: ValueError('When formatting, all partitions must have a path set'),
|
||||
lambda x: x.dev_path is None: ValueError("When formatting, all partitions must have a path set"),
|
||||
# crypto luks is not a valid file system type
|
||||
lambda x: x.fs_type is FilesystemType.Crypto_luks: ValueError(
|
||||
'Crypto luks cannot be set as a filesystem type'),
|
||||
lambda x: x.fs_type is FilesystemType.Crypto_luks: ValueError("Crypto luks cannot be set as a filesystem type"),
|
||||
# file system type must be set
|
||||
lambda x: x.fs_type is None: ValueError('File system type must be set for modification')
|
||||
lambda x: x.fs_type is None: ValueError("File system type must be set for modification"),
|
||||
}
|
||||
|
||||
for check, exc in checks.items():
|
||||
|
|
@ -144,7 +143,7 @@ class FilesystemHandler:
|
|||
raise exc
|
||||
|
||||
def perform_lvm_operations(self) -> None:
|
||||
info('Setting up LVM config...')
|
||||
info("Setting up LVM config...")
|
||||
|
||||
if not self._disk_config.lvm_config:
|
||||
return
|
||||
|
|
@ -152,7 +151,7 @@ class FilesystemHandler:
|
|||
if self._enc_config:
|
||||
self._setup_lvm_encrypted(
|
||||
self._disk_config.lvm_config,
|
||||
self._enc_config
|
||||
self._enc_config,
|
||||
)
|
||||
else:
|
||||
self._setup_lvm(self._disk_config.lvm_config)
|
||||
|
|
@ -190,7 +189,7 @@ class FilesystemHandler:
|
|||
def _setup_lvm(
|
||||
self,
|
||||
lvm_config: LvmConfiguration,
|
||||
enc_mods: dict[PartitionModification, Luks2] = {}
|
||||
enc_mods: dict[PartitionModification, Luks2] = {},
|
||||
) -> None:
|
||||
self._lvm_create_pvs(lvm_config, enc_mods)
|
||||
|
||||
|
|
@ -203,7 +202,7 @@ class FilesystemHandler:
|
|||
vg_info = device_handler.lvm_group_info(vg.name)
|
||||
|
||||
if not vg_info:
|
||||
raise ValueError('Unable to fetch VG info')
|
||||
raise ValueError("Unable to fetch VG info")
|
||||
|
||||
# the actual available LVM Group size will be smaller than the
|
||||
# total PVs size due to reserved metadata storage etc.
|
||||
|
|
@ -221,11 +220,11 @@ class FilesystemHandler:
|
|||
for lv in vg.volumes:
|
||||
offset = max_vol_offset if lv == max_vol else None
|
||||
|
||||
debug(f'vg: {vg.name}, vol: {lv.name}, offset: {offset}')
|
||||
debug(f"vg: {vg.name}, vol: {lv.name}, offset: {offset}")
|
||||
device_handler.lvm_vol_create(vg.name, lv, offset)
|
||||
|
||||
while True:
|
||||
debug('Fetching LVM volume info')
|
||||
debug("Fetching LVM volume info")
|
||||
lv_info = device_handler.lvm_vol_info(lv.name)
|
||||
if lv_info is not None:
|
||||
break
|
||||
|
|
@ -237,12 +236,12 @@ class FilesystemHandler:
|
|||
def _format_lvm_vols(
|
||||
self,
|
||||
lvm_config: LvmConfiguration,
|
||||
enc_vols: dict[LvmVolume, Luks2] = {}
|
||||
enc_vols: dict[LvmVolume, Luks2] = {},
|
||||
) -> None:
|
||||
for vol in lvm_config.get_all_volumes():
|
||||
if enc_vol := enc_vols.get(vol, None):
|
||||
if not enc_vol.mapper_dev:
|
||||
raise ValueError('No mapper device defined')
|
||||
raise ValueError("No mapper device defined")
|
||||
path = enc_vol.mapper_dev
|
||||
else:
|
||||
path = vol.safe_dev_path
|
||||
|
|
@ -257,7 +256,7 @@ class FilesystemHandler:
|
|||
def _lvm_create_pvs(
|
||||
self,
|
||||
lvm_config: LvmConfiguration,
|
||||
enc_mods: dict[PartitionModification, Luks2] = {}
|
||||
enc_mods: dict[PartitionModification, Luks2] = {},
|
||||
) -> None:
|
||||
pv_paths: set[Path] = set()
|
||||
|
||||
|
|
@ -269,7 +268,7 @@ class FilesystemHandler:
|
|||
def _get_all_pv_dev_paths(
|
||||
self,
|
||||
pvs: list[PartitionModification],
|
||||
enc_mods: dict[PartitionModification, Luks2] = {}
|
||||
enc_mods: dict[PartitionModification, Luks2] = {},
|
||||
) -> set[Path]:
|
||||
pv_paths: set[Path] = set()
|
||||
|
||||
|
|
@ -286,7 +285,7 @@ class FilesystemHandler:
|
|||
self,
|
||||
lvm_config: LvmConfiguration,
|
||||
enc_config: DiskEncryption,
|
||||
lock_after_create: bool = True
|
||||
lock_after_create: bool = True,
|
||||
) -> dict[LvmVolume, Luks2]:
|
||||
enc_vols: dict[LvmVolume, Luks2] = {}
|
||||
|
||||
|
|
@ -296,7 +295,7 @@ class FilesystemHandler:
|
|||
vol.safe_dev_path,
|
||||
vol.mapper_name,
|
||||
enc_config.encryption_password,
|
||||
lock_after_create
|
||||
lock_after_create,
|
||||
)
|
||||
|
||||
enc_vols[vol] = luks_handler
|
||||
|
|
@ -306,7 +305,7 @@ class FilesystemHandler:
|
|||
def _encrypt_partitions(
|
||||
self,
|
||||
enc_config: DiskEncryption,
|
||||
lock_after_create: bool = True
|
||||
lock_after_create: bool = True,
|
||||
) -> dict[PartitionModification, Luks2]:
|
||||
enc_mods: dict[PartitionModification, Luks2] = {}
|
||||
|
||||
|
|
@ -326,7 +325,7 @@ class FilesystemHandler:
|
|||
part_mod.safe_dev_path,
|
||||
part_mod.mapper_name,
|
||||
enc_config.encryption_password,
|
||||
lock_after_create=lock_after_create
|
||||
lock_after_create=lock_after_create,
|
||||
)
|
||||
|
||||
enc_mods[part_mod] = luks_handler
|
||||
|
|
@ -342,19 +341,19 @@ class FilesystemHandler:
|
|||
|
||||
device_handler.lvm_vol_reduce(
|
||||
largest_vol.safe_dev_path,
|
||||
Size(256, Unit.MiB, SectorSize.default())
|
||||
Size(256, Unit.MiB, SectorSize.default()),
|
||||
)
|
||||
|
||||
def _final_warning(self, device_paths: str) -> bool:
|
||||
# Issue a final warning before we continue with something un-revertable.
|
||||
# We mention the drive one last time, and count from 5 to 0.
|
||||
out = str(_(' ! Formatting {} in ')).format(device_paths)
|
||||
Tui.print(out, row=0, endl='', clear_screen=True)
|
||||
out = str(_(" ! Formatting {} in ")).format(device_paths)
|
||||
Tui.print(out, row=0, endl="", clear_screen=True)
|
||||
|
||||
try:
|
||||
countdown = '\n5...4...3...2...1\n'
|
||||
countdown = "\n5...4...3...2...1\n"
|
||||
for c in countdown:
|
||||
Tui.print(c, row=0, endl='')
|
||||
Tui.print(c, row=0, endl="")
|
||||
time.sleep(0.25)
|
||||
except KeyboardInterrupt:
|
||||
with Tui():
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ class FreeSpace:
|
|||
Called for displaying data in table format
|
||||
"""
|
||||
return {
|
||||
'Start': self.start.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
|
||||
'End': self.end.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
|
||||
'Size': self.length.format_highest(),
|
||||
"Start": self.start.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
|
||||
"End": self.end.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
|
||||
"Size": self.length.format_highest(),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ class DiskSegment:
|
|||
length=self.segment.length,
|
||||
)
|
||||
data = part_mod.table_data()
|
||||
data.update({'Status': 'free', 'Type': '', 'FS type': ''})
|
||||
data.update({"Status": "free", "Type": "", "FS type": ""})
|
||||
return data
|
||||
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
def __init__(
|
||||
self,
|
||||
device_mod: DeviceModification,
|
||||
partition_table: PartitionTable
|
||||
partition_table: PartitionTable,
|
||||
) -> None:
|
||||
device = device_mod.device
|
||||
|
||||
|
|
@ -91,24 +91,28 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
self._using_gpt = device_mod.using_gpt(partition_table)
|
||||
|
||||
self._actions = {
|
||||
'suggest_partition_layout': str(_('Suggest partition layout')),
|
||||
'remove_added_partitions': str(_('Remove all newly added partitions')),
|
||||
'assign_mountpoint': str(_('Assign mountpoint')),
|
||||
'mark_formatting': str(_('Mark/Unmark to be formatted (wipes data)')),
|
||||
'mark_bootable': str(_('Mark/Unmark as bootable')),
|
||||
"suggest_partition_layout": str(_("Suggest partition layout")),
|
||||
"remove_added_partitions": str(_("Remove all newly added partitions")),
|
||||
"assign_mountpoint": str(_("Assign mountpoint")),
|
||||
"mark_formatting": str(_("Mark/Unmark to be formatted (wipes data)")),
|
||||
"mark_bootable": str(_("Mark/Unmark as bootable")),
|
||||
}
|
||||
if self._using_gpt:
|
||||
self._actions.update({
|
||||
'mark_esp': str(_('Mark/Unmark as ESP')),
|
||||
'mark_xbootldr': str(_('Mark/Unmark as XBOOTLDR'))
|
||||
})
|
||||
self._actions.update({
|
||||
'set_filesystem': str(_('Change filesystem')),
|
||||
'btrfs_mark_compressed': str(_('Mark/Unmark as compressed')), # btrfs only
|
||||
'btrfs_mark_nodatacow': str(_('Mark/Unmark as nodatacow')), # btrfs only
|
||||
'btrfs_set_subvolumes': str(_('Set subvolumes')), # btrfs only
|
||||
'delete_partition': str(_('Delete partition'))
|
||||
})
|
||||
self._actions.update(
|
||||
{
|
||||
"mark_esp": str(_("Mark/Unmark as ESP")),
|
||||
"mark_xbootldr": str(_("Mark/Unmark as XBOOTLDR")),
|
||||
}
|
||||
)
|
||||
self._actions.update(
|
||||
{
|
||||
"set_filesystem": str(_("Change filesystem")),
|
||||
"btrfs_mark_compressed": str(_("Mark/Unmark as compressed")), # btrfs only
|
||||
"btrfs_mark_nodatacow": str(_("Mark/Unmark as nodatacow")), # btrfs only
|
||||
"btrfs_set_subvolumes": str(_("Set subvolumes")), # btrfs only
|
||||
"delete_partition": str(_("Delete partition")),
|
||||
}
|
||||
)
|
||||
|
||||
device_partitions = []
|
||||
|
||||
|
|
@ -116,25 +120,25 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
# we'll display the existing partitions of the device
|
||||
for partition in device.partition_infos:
|
||||
device_partitions.append(
|
||||
PartitionModification.from_existing_partition(partition)
|
||||
PartitionModification.from_existing_partition(partition),
|
||||
)
|
||||
else:
|
||||
device_partitions = device_mod.partitions
|
||||
|
||||
prompt = str(_('Partition management: {}')).format(device.device_info.path) + '\n'
|
||||
prompt += str(_('Total length: {}')).format(device.device_info.total_size.format_size(Unit.MiB))
|
||||
self._info = prompt + '\n'
|
||||
prompt = str(_("Partition management: {}")).format(device.device_info.path) + "\n"
|
||||
prompt += str(_("Total length: {}")).format(device.device_info.total_size.format_size(Unit.MiB))
|
||||
self._info = prompt + "\n"
|
||||
|
||||
display_actions = list(self._actions.values())
|
||||
super().__init__(
|
||||
self.as_segments(device_partitions),
|
||||
display_actions[:1],
|
||||
display_actions[2:],
|
||||
self._info + self.wipe_str()
|
||||
self._info + self.wipe_str(),
|
||||
)
|
||||
|
||||
def wipe_str(self) -> str:
|
||||
return '{}: {}'.format(str(_('Wipe')), self._wipe)
|
||||
return "{}: {}".format(str(_("Wipe")), self._wipe)
|
||||
|
||||
def as_segments(self, device_partitions: list[PartitionModification]) -> list[DiskSegment]:
|
||||
end = self._device.device_info.total_size
|
||||
|
|
@ -157,9 +161,9 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
return segments
|
||||
|
||||
first_part_index, first_partition = next(
|
||||
(i, disk_segment) for i, disk_segment in enumerate(segments)
|
||||
if isinstance(disk_segment.segment, PartitionModification)
|
||||
and not disk_segment.segment.is_delete()
|
||||
(i, disk_segment)
|
||||
for i, disk_segment in enumerate(segments)
|
||||
if isinstance(disk_segment.segment, PartitionModification) and not disk_segment.segment.is_delete()
|
||||
)
|
||||
|
||||
prev_partition = first_partition
|
||||
|
|
@ -193,10 +197,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
|
||||
@staticmethod
|
||||
def get_part_mods(disk_segments: list[DiskSegment]) -> list[PartitionModification]:
|
||||
return [
|
||||
s.segment for s in disk_segments
|
||||
if isinstance(s.segment, PartitionModification)
|
||||
]
|
||||
return [s.segment for s in disk_segments if isinstance(s.segment, PartitionModification)]
|
||||
|
||||
def get_device_mod(self) -> DeviceModification:
|
||||
disk_segments = super().run()
|
||||
|
|
@ -207,7 +208,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
def _run_actions_on_entry(self, entry: DiskSegment) -> None:
|
||||
# Do not create a menu when the segment is free space
|
||||
if isinstance(entry.segment, FreeSpace):
|
||||
self._data = self.handle_action('', entry, self._data)
|
||||
self._data = self.handle_action("", entry, self._data)
|
||||
else:
|
||||
super()._run_actions_on_entry(entry)
|
||||
|
||||
|
|
@ -215,18 +216,18 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
def selected_action_display(self, selection: DiskSegment) -> str:
|
||||
if isinstance(selection.segment, PartitionModification):
|
||||
if selection.segment.status == ModificationStatus.Create:
|
||||
return str(_('Partition - New'))
|
||||
return str(_("Partition - New"))
|
||||
elif selection.segment.is_delete() and selection.segment.dev_path:
|
||||
title = str(_('Partition')) + '\n\n'
|
||||
title += 'status: delete\n'
|
||||
title += f'device: {selection.segment.dev_path}\n'
|
||||
title = str(_("Partition")) + "\n\n"
|
||||
title += "status: delete\n"
|
||||
title += f"device: {selection.segment.dev_path}\n"
|
||||
for part in self._device.partition_infos:
|
||||
if part.path == selection.segment.dev_path:
|
||||
if part.partuuid:
|
||||
title += f'partuuid: {part.partuuid}'
|
||||
title += f"partuuid: {part.partuuid}"
|
||||
return title
|
||||
return str(selection.segment.dev_path)
|
||||
return ''
|
||||
return ""
|
||||
|
||||
@override
|
||||
def filter_options(self, selection: DiskSegment, options: list[str]) -> list[str]:
|
||||
|
|
@ -237,7 +238,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
not_filter = list(self._actions.values())
|
||||
# only display formatting if the partition exists already
|
||||
elif not selection.segment.exists():
|
||||
not_filter += [self._actions['mark_formatting']]
|
||||
not_filter += [self._actions["mark_formatting"]]
|
||||
else:
|
||||
# only allow options if the existing partition
|
||||
# was marked as formatting, otherwise we run into issues where
|
||||
|
|
@ -245,29 +246,29 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
# 2. Switch back to old filesystem -> should unmark wipe now, but
|
||||
# how do we know it was the original one?
|
||||
not_filter += [
|
||||
self._actions['set_filesystem'],
|
||||
self._actions['mark_bootable']
|
||||
self._actions["set_filesystem"],
|
||||
self._actions["mark_bootable"],
|
||||
]
|
||||
if self._using_gpt:
|
||||
not_filter += [
|
||||
self._actions['mark_esp'],
|
||||
self._actions['mark_xbootldr']
|
||||
self._actions["mark_esp"],
|
||||
self._actions["mark_xbootldr"],
|
||||
]
|
||||
not_filter += [
|
||||
self._actions['btrfs_mark_compressed'],
|
||||
self._actions['btrfs_mark_nodatacow'],
|
||||
self._actions['btrfs_set_subvolumes']
|
||||
self._actions["btrfs_mark_compressed"],
|
||||
self._actions["btrfs_mark_nodatacow"],
|
||||
self._actions["btrfs_set_subvolumes"],
|
||||
]
|
||||
|
||||
# non btrfs partitions shouldn't get btrfs options
|
||||
if selection.segment.fs_type != FilesystemType.Btrfs:
|
||||
not_filter += [
|
||||
self._actions['btrfs_mark_compressed'],
|
||||
self._actions['btrfs_mark_nodatacow'],
|
||||
self._actions['btrfs_set_subvolumes']
|
||||
self._actions["btrfs_mark_compressed"],
|
||||
self._actions["btrfs_mark_nodatacow"],
|
||||
self._actions["btrfs_set_subvolumes"],
|
||||
]
|
||||
else:
|
||||
not_filter += [self._actions['assign_mountpoint']]
|
||||
not_filter += [self._actions["assign_mountpoint"]]
|
||||
|
||||
return [o for o in options if o not in not_filter]
|
||||
|
||||
|
|
@ -276,30 +277,26 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
self,
|
||||
action: str,
|
||||
entry: DiskSegment | None,
|
||||
data: list[DiskSegment]
|
||||
data: list[DiskSegment],
|
||||
) -> list[DiskSegment]:
|
||||
if not entry:
|
||||
action_key = [k for k, v in self._actions.items() if v == action][0]
|
||||
match action_key:
|
||||
case 'suggest_partition_layout':
|
||||
case "suggest_partition_layout":
|
||||
part_mods = self.get_part_mods(data)
|
||||
device_mod = self._suggest_partition_layout(part_mods)
|
||||
if device_mod and device_mod.partitions:
|
||||
data = self.as_segments(device_mod.partitions)
|
||||
self._wipe = device_mod.wipe
|
||||
self._prompt = self._info + self.wipe_str()
|
||||
case 'remove_added_partitions':
|
||||
case "remove_added_partitions":
|
||||
if self._reset_confirmation():
|
||||
data = [
|
||||
s for s in data
|
||||
if isinstance(s.segment, PartitionModification)
|
||||
and s.segment.is_exists_or_modify()
|
||||
]
|
||||
data = [s for s in data if isinstance(s.segment, PartitionModification) and s.segment.is_exists_or_modify()]
|
||||
elif isinstance(entry.segment, PartitionModification):
|
||||
partition = entry.segment
|
||||
action_key = [k for k, v in self._actions.items() if v == action][0]
|
||||
match action_key:
|
||||
case 'assign_mountpoint':
|
||||
case "assign_mountpoint":
|
||||
new_mountpoint = self._prompt_mountpoint()
|
||||
if not partition.is_swap():
|
||||
if partition.is_home():
|
||||
|
|
@ -315,22 +312,22 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
if partition.is_home():
|
||||
partition.flags = []
|
||||
partition.set_flag(PartitionFlag.LINUX_HOME)
|
||||
case 'mark_formatting':
|
||||
case "mark_formatting":
|
||||
self._prompt_formatting(partition)
|
||||
case 'mark_bootable':
|
||||
case "mark_bootable":
|
||||
if not partition.is_swap():
|
||||
partition.invert_flag(PartitionFlag.BOOT)
|
||||
case 'mark_esp':
|
||||
case "mark_esp":
|
||||
if not partition.is_root() and not partition.is_home() and not partition.is_swap():
|
||||
if PartitionFlag.XBOOTLDR in partition.flags:
|
||||
partition.invert_flag(PartitionFlag.XBOOTLDR)
|
||||
partition.invert_flag(PartitionFlag.ESP)
|
||||
case 'mark_xbootldr':
|
||||
case "mark_xbootldr":
|
||||
if not partition.is_root() and not partition.is_home() and not partition.is_swap():
|
||||
if PartitionFlag.ESP in partition.flags:
|
||||
partition.invert_flag(PartitionFlag.ESP)
|
||||
partition.invert_flag(PartitionFlag.XBOOTLDR)
|
||||
case 'set_filesystem':
|
||||
case "set_filesystem":
|
||||
fs_type = self._prompt_partition_fs_type()
|
||||
if fs_type:
|
||||
if partition.is_swap():
|
||||
|
|
@ -343,13 +340,13 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
# btrfs subvolumes will define mountpoints
|
||||
if fs_type == FilesystemType.Btrfs:
|
||||
partition.mountpoint = None
|
||||
case 'btrfs_mark_compressed':
|
||||
case "btrfs_mark_compressed":
|
||||
self._toggle_mount_option(partition, BtrfsMountOption.compress)
|
||||
case 'btrfs_mark_nodatacow':
|
||||
case "btrfs_mark_nodatacow":
|
||||
self._toggle_mount_option(partition, BtrfsMountOption.nodatacow)
|
||||
case 'btrfs_set_subvolumes':
|
||||
case "btrfs_set_subvolumes":
|
||||
self._set_btrfs_subvolumes(partition)
|
||||
case 'delete_partition':
|
||||
case "delete_partition":
|
||||
data = self._delete_partition(partition, data)
|
||||
else:
|
||||
part_mods = self.get_part_mods(data)
|
||||
|
|
@ -362,47 +359,35 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
def _delete_partition(
|
||||
self,
|
||||
entry: PartitionModification,
|
||||
data: list[DiskSegment]
|
||||
data: list[DiskSegment],
|
||||
) -> list[DiskSegment]:
|
||||
if entry.is_exists_or_modify():
|
||||
entry.status = ModificationStatus.Delete
|
||||
part_mods = self.get_part_mods(data)
|
||||
else:
|
||||
part_mods = [
|
||||
d.segment for d in data
|
||||
if isinstance(d.segment, PartitionModification)
|
||||
and d.segment != entry
|
||||
]
|
||||
part_mods = [d.segment for d in data if isinstance(d.segment, PartitionModification) and d.segment != entry]
|
||||
|
||||
return self.as_segments(part_mods)
|
||||
|
||||
def _toggle_mount_option(
|
||||
self,
|
||||
partition: PartitionModification,
|
||||
option: BtrfsMountOption
|
||||
option: BtrfsMountOption,
|
||||
) -> None:
|
||||
if option.value not in partition.mount_options:
|
||||
if option == BtrfsMountOption.compress:
|
||||
partition.mount_options = [
|
||||
o for o in partition.mount_options
|
||||
if o != BtrfsMountOption.nodatacow.value
|
||||
]
|
||||
partition.mount_options = [o for o in partition.mount_options if o != BtrfsMountOption.nodatacow.value]
|
||||
|
||||
partition.mount_options = [
|
||||
o for o in partition.mount_options
|
||||
if not o.startswith(BtrfsMountOption.compress.name)
|
||||
]
|
||||
partition.mount_options = [o for o in partition.mount_options if not o.startswith(BtrfsMountOption.compress.name)]
|
||||
|
||||
partition.mount_options.append(option.value)
|
||||
else:
|
||||
partition.mount_options = [
|
||||
o for o in partition.mount_options if o != option.value
|
||||
]
|
||||
partition.mount_options = [o for o in partition.mount_options if o != option.value]
|
||||
|
||||
def _set_btrfs_subvolumes(self, partition: PartitionModification) -> None:
|
||||
partition.btrfs_subvols = SubvolumeMenu(
|
||||
partition.btrfs_subvols,
|
||||
None
|
||||
None,
|
||||
).run()
|
||||
|
||||
def _prompt_formatting(self, partition: PartitionModification) -> None:
|
||||
|
|
@ -417,7 +402,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
# without asking the user which inner-filesystem they want to use. Since the flag 'encrypted' = True is already set,
|
||||
# it's safe to change the filesystem for this partition.
|
||||
if partition.fs_type == FilesystemType.Crypto_luks:
|
||||
prompt = str(_('This partition is currently encrypted, to format it a filesystem has to be specified')) + '\n'
|
||||
prompt = str(_("This partition is currently encrypted, to format it a filesystem has to be specified")) + "\n"
|
||||
fs_type = self._prompt_partition_fs_type(prompt)
|
||||
partition.fs_type = fs_type
|
||||
|
||||
|
|
@ -425,8 +410,8 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
partition.mountpoint = None
|
||||
|
||||
def _prompt_mountpoint(self) -> Path:
|
||||
header = str(_('Partition mount-points are relative to inside the installation, the boot would be /boot as an example.')) + '\n'
|
||||
prompt = str(_('Mountpoint'))
|
||||
header = str(_("Partition mount-points are relative to inside the installation, the boot would be /boot as an example.")) + "\n"
|
||||
prompt = str(_("Mountpoint"))
|
||||
|
||||
mountpoint = prompt_dir(prompt, header, validate=False, allow_skip=False)
|
||||
assert mountpoint
|
||||
|
|
@ -442,30 +427,30 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
group,
|
||||
header=prompt,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Filesystem'))),
|
||||
allow_skip=False
|
||||
frame=FrameProperties.min(str(_("Filesystem"))),
|
||||
allow_skip=False,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case _:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
def _validate_value(
|
||||
self,
|
||||
sector_size: SectorSize,
|
||||
max_size: Size,
|
||||
text: str
|
||||
text: str,
|
||||
) -> Size | None:
|
||||
match = re.match(r'([0-9]+)([a-zA-Z|%]*)', text, re.I)
|
||||
match = re.match(r"([0-9]+)([a-zA-Z|%]*)", text, re.I)
|
||||
|
||||
if not match:
|
||||
return None
|
||||
|
||||
str_value, unit = match.groups()
|
||||
|
||||
if unit == '%':
|
||||
if unit == "%":
|
||||
value = int(max_size.value * (int(str_value) / 100))
|
||||
unit = max_size.unit.name
|
||||
else:
|
||||
|
|
@ -488,32 +473,32 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
def validate(value: str) -> str | None:
|
||||
size = self._validate_value(sector_size, max_size, value)
|
||||
if not size:
|
||||
return str(_('Invalid size'))
|
||||
return str(_("Invalid size"))
|
||||
return None
|
||||
|
||||
device_info = self._device.device_info
|
||||
sector_size = device_info.sector_size
|
||||
|
||||
text = str(_('Selected free space segment on device {}:')).format(device_info.path) + '\n\n'
|
||||
text = str(_("Selected free space segment on device {}:")).format(device_info.path) + "\n\n"
|
||||
free_space_table = FormattedOutput.as_table([free_space])
|
||||
prompt = text + free_space_table + '\n'
|
||||
prompt = text + free_space_table + "\n"
|
||||
|
||||
max_sectors = free_space.length.format_size(Unit.sectors, sector_size)
|
||||
max_bytes = free_space.length.format_size(Unit.B)
|
||||
|
||||
prompt += str(_('Size: {} / {}')).format(max_sectors, max_bytes) + '\n\n'
|
||||
prompt += str(_('All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB...')) + '\n'
|
||||
prompt += str(_('If no unit is provided, the value is interpreted as sectors')) + '\n'
|
||||
prompt += str(_("Size: {} / {}")).format(max_sectors, max_bytes) + "\n\n"
|
||||
prompt += str(_("All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB...")) + "\n"
|
||||
prompt += str(_("If no unit is provided, the value is interpreted as sectors")) + "\n"
|
||||
|
||||
max_size = free_space.length
|
||||
|
||||
title = str(_('Size (default: {}): ')).format(max_size.format_highest())
|
||||
title = str(_("Size (default: {}): ")).format(max_size.format_highest())
|
||||
|
||||
result = EditMenu(
|
||||
title,
|
||||
header=f'{prompt}\b',
|
||||
header=f"{prompt}\b",
|
||||
allow_skip=True,
|
||||
validator=validate
|
||||
validator=validate,
|
||||
).input()
|
||||
|
||||
size: Size | None = None
|
||||
|
|
@ -547,10 +532,10 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
start=free_space.start,
|
||||
length=length,
|
||||
fs_type=fs_type,
|
||||
mountpoint=mountpoint
|
||||
mountpoint=mountpoint,
|
||||
)
|
||||
|
||||
if partition.mountpoint == Path('/boot'):
|
||||
if partition.mountpoint == Path("/boot"):
|
||||
partition.set_flag(PartitionFlag.BOOT)
|
||||
if self._using_gpt:
|
||||
partition.set_flag(PartitionFlag.ESP)
|
||||
|
|
@ -562,7 +547,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
return partition
|
||||
|
||||
def _reset_confirmation(self) -> bool:
|
||||
prompt = str(_('This will remove all newly added partitions, continue?')) + '\n'
|
||||
prompt = str(_("This will remove all newly added partitions, continue?")) + "\n"
|
||||
|
||||
result = SelectMenu[bool](
|
||||
MenuItemGroup.yes_no(),
|
||||
|
|
@ -571,14 +556,14 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
orientation=Orientation.HORIZONTAL,
|
||||
columns=2,
|
||||
reset_warning_msg=prompt,
|
||||
allow_skip=False
|
||||
allow_skip=False,
|
||||
).run()
|
||||
|
||||
return result.item() == MenuItem.yes()
|
||||
|
||||
def _suggest_partition_layout(
|
||||
self,
|
||||
data: list[PartitionModification]
|
||||
data: list[PartitionModification],
|
||||
) -> DeviceModification | None:
|
||||
# if modifications have been done already, inform the user
|
||||
# that this operation will erase those modifications
|
||||
|
|
@ -593,7 +578,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
|
||||
def manual_partitioning(
|
||||
device_mod: DeviceModification,
|
||||
partition_table: PartitionTable
|
||||
partition_table: PartitionTable,
|
||||
) -> DeviceModification | None:
|
||||
menu_list = PartitioningList(device_mod, partition_table)
|
||||
mod = menu_list.get_device_mod()
|
||||
|
|
|
|||
|
|
@ -21,19 +21,19 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
|
|||
def __init__(
|
||||
self,
|
||||
btrfs_subvols: list[SubvolumeModification],
|
||||
prompt: str | None = None
|
||||
prompt: str | None = None,
|
||||
):
|
||||
self._actions = [
|
||||
str(_('Add subvolume')),
|
||||
str(_('Edit subvolume')),
|
||||
str(_('Delete subvolume'))
|
||||
str(_("Add subvolume")),
|
||||
str(_("Edit subvolume")),
|
||||
str(_("Delete subvolume")),
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
btrfs_subvols,
|
||||
[self._actions[0]],
|
||||
self._actions[1:],
|
||||
prompt
|
||||
prompt,
|
||||
)
|
||||
|
||||
@override
|
||||
|
|
@ -42,10 +42,10 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
|
|||
|
||||
def _add_subvolume(self, preset: SubvolumeModification | None = None) -> SubvolumeModification | None:
|
||||
result = EditMenu(
|
||||
str(_('Subvolume name')),
|
||||
str(_("Subvolume name")),
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True,
|
||||
default_text=str(preset.name) if preset else None
|
||||
default_text=str(preset.name) if preset else None,
|
||||
).input()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -54,7 +54,7 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
|
|||
case ResultType.Selection:
|
||||
name = result.text()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
case _:
|
||||
assert_never(result.type_)
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
|
|||
str(_("Subvolume mountpoint")),
|
||||
header=header,
|
||||
allow_skip=True,
|
||||
validate=False
|
||||
validate=False,
|
||||
)
|
||||
|
||||
if not path:
|
||||
|
|
@ -77,7 +77,7 @@ class SubvolumeMenu(ListManager[SubvolumeModification]):
|
|||
self,
|
||||
action: str,
|
||||
entry: SubvolumeModification | None,
|
||||
data: list[SubvolumeModification]
|
||||
data: list[SubvolumeModification],
|
||||
) -> list[SubvolumeModification]:
|
||||
if action == self._actions[0]: # add
|
||||
new_subvolume = self._add_subvolume()
|
||||
|
|
|
|||
|
|
@ -15,15 +15,15 @@ class LsblkOutput(BaseModel):
|
|||
def _fetch_lsblk_info(
|
||||
dev_path: Path | str | None = None,
|
||||
reverse: bool = False,
|
||||
full_dev_path: bool = False
|
||||
full_dev_path: bool = False,
|
||||
) -> LsblkOutput:
|
||||
cmd = ['lsblk', '--json', '--bytes', '--output', ','.join(LsblkInfo.fields())]
|
||||
cmd = ["lsblk", "--json", "--bytes", "--output", ",".join(LsblkInfo.fields())]
|
||||
|
||||
if reverse:
|
||||
cmd.append('--inverse')
|
||||
cmd.append("--inverse")
|
||||
|
||||
if full_dev_path:
|
||||
cmd.append('--paths')
|
||||
cmd.append("--paths")
|
||||
|
||||
if dev_path:
|
||||
cmd.append(str(dev_path))
|
||||
|
|
@ -33,7 +33,7 @@ def _fetch_lsblk_info(
|
|||
except SysCallError as err:
|
||||
# Get the output minus the message/info from lsblk if it returns a non-zero exit code.
|
||||
if err.worker_log:
|
||||
debug(f'Error calling lsblk: {err.worker_log.decode()}')
|
||||
debug(f"Error calling lsblk: {err.worker_log.decode()}")
|
||||
|
||||
if dev_path:
|
||||
raise DiskError(f'Failed to read disk "{dev_path}" with lsblk')
|
||||
|
|
@ -47,7 +47,7 @@ def _fetch_lsblk_info(
|
|||
def get_lsblk_info(
|
||||
dev_path: Path | str,
|
||||
reverse: bool = False,
|
||||
full_dev_path: bool = False
|
||||
full_dev_path: bool = False,
|
||||
) -> LsblkInfo:
|
||||
infos = _fetch_lsblk_info(dev_path, reverse=reverse, full_dev_path=full_dev_path)
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ def get_lsblk_output() -> LsblkOutput:
|
|||
|
||||
def find_lsblk_info(
|
||||
dev_path: Path | str,
|
||||
info: list[LsblkInfo]
|
||||
info: list[LsblkInfo],
|
||||
) -> LsblkInfo | None:
|
||||
if isinstance(dev_path, str):
|
||||
dev_path = Path(dev_path)
|
||||
|
|
@ -105,7 +105,7 @@ def disk_layouts() -> str:
|
|||
lsblk_output = get_lsblk_output()
|
||||
except SysCallError as err:
|
||||
warn(f"Could not return disk layouts: {err}")
|
||||
return ''
|
||||
return ""
|
||||
|
||||
return lsblk_output.model_dump_json(indent=4)
|
||||
|
||||
|
|
@ -116,13 +116,13 @@ def umount(mountpoint: Path, recursive: bool = False) -> None:
|
|||
if not lsblk_info.mountpoints:
|
||||
return
|
||||
|
||||
debug(f'Partition {mountpoint} is currently mounted at: {[str(m) for m in lsblk_info.mountpoints]}')
|
||||
debug(f"Partition {mountpoint} is currently mounted at: {[str(m) for m in lsblk_info.mountpoints]}")
|
||||
|
||||
cmd = ['umount']
|
||||
cmd = ["umount"]
|
||||
|
||||
if recursive:
|
||||
cmd.append('-R')
|
||||
cmd.append("-R")
|
||||
|
||||
for path in lsblk_info.mountpoints:
|
||||
debug(f'Unmounting mountpoint: {path}')
|
||||
debug(f"Unmounting mountpoint: {path}")
|
||||
SysCommand(cmd + [str(path)])
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class UnknownFilesystemFormat(Exception):
|
|||
|
||||
|
||||
class SysCallError(Exception):
|
||||
def __init__(self, message: str, exit_code: int | None = None, worker_log: bytes = b'') -> None:
|
||||
def __init__(self, message: str, exit_code: int | None = None, worker_log: bytes = b"") -> None:
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.exit_code = exit_code
|
||||
|
|
@ -35,6 +35,6 @@ class Deprecated(Exception):
|
|||
|
||||
|
||||
class DownloadTimeout(Exception):
|
||||
'''
|
||||
"""
|
||||
Download timeout exception raised by DownloadTimer.
|
||||
'''
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ from .output import debug, error
|
|||
from .storage import storage
|
||||
|
||||
# https://stackoverflow.com/a/43627833/929999
|
||||
_VT100_ESCAPE_REGEX = r'\x1B\[[?0-9;]*[a-zA-Z]'
|
||||
_VT100_ESCAPE_REGEX = r"\x1B\[[?0-9;]*[a-zA-Z]"
|
||||
_VT100_ESCAPE_REGEX_BYTES = _VT100_ESCAPE_REGEX.encode()
|
||||
|
||||
|
||||
def generate_password(length: int = 64) -> str:
|
||||
haystack = string.printable # digits, ascii_letters, punctuation (!"#$[] etc) and whitespace
|
||||
return ''.join(secrets.choice(haystack) for _ in range(length))
|
||||
return "".join(secrets.choice(haystack) for _ in range(length))
|
||||
|
||||
|
||||
def locate_binary(name: str) -> str:
|
||||
|
|
@ -39,11 +39,11 @@ def locate_binary(name: str) -> str:
|
|||
|
||||
|
||||
def clear_vt100_escape_codes(data: bytes) -> bytes:
|
||||
return re.sub(_VT100_ESCAPE_REGEX_BYTES, b'', data)
|
||||
return re.sub(_VT100_ESCAPE_REGEX_BYTES, b"", data)
|
||||
|
||||
|
||||
def clear_vt100_escape_codes_from_str(data: str) -> str:
|
||||
return re.sub(_VT100_ESCAPE_REGEX, '', data)
|
||||
return re.sub(_VT100_ESCAPE_REGEX, "", data)
|
||||
|
||||
|
||||
def jsonify(obj: object, safe: bool = True) -> object:
|
||||
|
|
@ -57,12 +57,11 @@ def jsonify(obj: object, safe: bool = True) -> object:
|
|||
return {
|
||||
key: jsonify(value, safe)
|
||||
for key, value in obj.items()
|
||||
if isinstance(key, compatible_types)
|
||||
and not (isinstance(key, str) and key.startswith("!") and safe)
|
||||
if isinstance(key, compatible_types) and not (isinstance(key, str) and key.startswith("!") and safe)
|
||||
}
|
||||
if isinstance(obj, Enum):
|
||||
return obj.value
|
||||
if hasattr(obj, 'json'):
|
||||
if hasattr(obj, "json"):
|
||||
# json() is a friendly name for json-helper, it should return
|
||||
# a dictionary representation of the object so that it can be
|
||||
# processed by the json library.
|
||||
|
|
@ -105,26 +104,26 @@ class SysCommandWorker:
|
|||
cmd: str | list[str],
|
||||
peek_output: bool | None = False,
|
||||
environment_vars: dict[str, str] | None = None,
|
||||
working_directory: str | None = './',
|
||||
remove_vt100_escape_codes_from_lines: bool = True
|
||||
working_directory: str | None = "./",
|
||||
remove_vt100_escape_codes_from_lines: bool = True,
|
||||
):
|
||||
if isinstance(cmd, str):
|
||||
cmd = shlex.split(cmd)
|
||||
|
||||
if cmd and not cmd[0].startswith(('/', './')): # Path() does not work well
|
||||
if cmd and not cmd[0].startswith(("/", "./")): # Path() does not work well
|
||||
cmd[0] = locate_binary(cmd[0])
|
||||
|
||||
self.cmd = cmd
|
||||
self.peek_output = peek_output
|
||||
# define the standard locale for command outputs. For now the C ascii one. Can be overridden
|
||||
self.environment_vars = {'LC_ALL': 'C'}
|
||||
self.environment_vars = {"LC_ALL": "C"}
|
||||
if environment_vars:
|
||||
self.environment_vars.update(environment_vars)
|
||||
|
||||
self.working_directory = working_directory
|
||||
|
||||
self.exit_code: int | None = None
|
||||
self._trace_log = b''
|
||||
self._trace_log = b""
|
||||
self._trace_log_pos = 0
|
||||
self.poll_object = epoll()
|
||||
self.child_fd: int | None = None
|
||||
|
|
@ -147,13 +146,13 @@ class SysCommandWorker:
|
|||
return False
|
||||
|
||||
def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]:
|
||||
last_line = self._trace_log.rfind(b'\n')
|
||||
lines = filter(None, self._trace_log[self._trace_log_pos:last_line].splitlines())
|
||||
last_line = self._trace_log.rfind(b"\n")
|
||||
lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines())
|
||||
for line in lines:
|
||||
if self.remove_vt100_escape_codes_from_lines:
|
||||
line = clear_vt100_escape_codes(line)
|
||||
|
||||
yield line + b'\n'
|
||||
yield line + b"\n"
|
||||
|
||||
self._trace_log_pos = last_line
|
||||
|
||||
|
|
@ -165,11 +164,11 @@ class SysCommandWorker:
|
|||
@override
|
||||
def __str__(self) -> str:
|
||||
try:
|
||||
return self._trace_log.decode('utf-8')
|
||||
return self._trace_log.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return str(self._trace_log)
|
||||
|
||||
def __enter__(self) -> 'SysCommandWorker':
|
||||
def __enter__(self) -> "SysCommandWorker":
|
||||
return self
|
||||
|
||||
def __exit__(self, *args: str) -> None:
|
||||
|
|
@ -195,7 +194,7 @@ class SysCommandWorker:
|
|||
raise SysCallError(
|
||||
f"{self.cmd} exited with abnormal exit code [{self.exit_code}]: {str(self)[-500:]}",
|
||||
self.exit_code,
|
||||
worker_log=self._trace_log
|
||||
worker_log=self._trace_log,
|
||||
)
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
|
|
@ -212,7 +211,7 @@ class SysCommandWorker:
|
|||
self.make_sure_we_are_executing()
|
||||
|
||||
if self.child_fd:
|
||||
return os.write(self.child_fd, data + (b'\n' if line_ending else b''))
|
||||
return os.write(self.child_fd, data + (b"\n" if line_ending else b""))
|
||||
|
||||
return 0
|
||||
|
||||
|
|
@ -234,7 +233,7 @@ class SysCommandWorker:
|
|||
if self.peek_output:
|
||||
if isinstance(output, bytes):
|
||||
try:
|
||||
output = output.decode('UTF-8')
|
||||
output = output.decode("UTF-8")
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
|
||||
|
|
@ -314,7 +313,7 @@ class SysCommandWorker:
|
|||
|
||||
return True
|
||||
|
||||
def decode(self, encoding: str = 'UTF-8') -> str:
|
||||
def decode(self, encoding: str = "UTF-8") -> str:
|
||||
return self._trace_log.decode(encoding)
|
||||
|
||||
|
||||
|
|
@ -324,9 +323,9 @@ class SysCommand:
|
|||
cmd: str | list[str],
|
||||
peek_output: bool | None = False,
|
||||
environment_vars: dict[str, str] | None = None,
|
||||
working_directory: str | None = './',
|
||||
remove_vt100_escape_codes_from_lines: bool = True):
|
||||
|
||||
working_directory: str | None = "./",
|
||||
remove_vt100_escape_codes_from_lines: bool = True,
|
||||
):
|
||||
self.cmd = cmd
|
||||
self.peek_output = peek_output
|
||||
self.environment_vars = environment_vars
|
||||
|
|
@ -363,7 +362,7 @@ class SysCommand:
|
|||
|
||||
@override
|
||||
def __repr__(self, *args: list[Any], **kwargs: dict[str, Any]) -> str:
|
||||
return self.decode('UTF-8', errors='backslashreplace') or ''
|
||||
return self.decode("UTF-8", errors="backslashreplace") or ""
|
||||
|
||||
def create_session(self) -> bool:
|
||||
"""
|
||||
|
|
@ -379,22 +378,22 @@ class SysCommand:
|
|||
peek_output=self.peek_output,
|
||||
environment_vars=self.environment_vars,
|
||||
remove_vt100_escape_codes_from_lines=self.remove_vt100_escape_codes_from_lines,
|
||||
working_directory=self.working_directory) as session:
|
||||
|
||||
working_directory=self.working_directory,
|
||||
) as session:
|
||||
self.session = session
|
||||
|
||||
while not self.session.ended:
|
||||
self.session.poll()
|
||||
|
||||
if self.peek_output:
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.write("\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
return True
|
||||
|
||||
def decode(self, encoding: str = 'utf-8', errors: str = 'backslashreplace', strip: bool = True) -> str:
|
||||
def decode(self, encoding: str = "utf-8", errors: str = "backslashreplace", strip: bool = True) -> str:
|
||||
if not self.session:
|
||||
raise ValueError('No session available to decode')
|
||||
raise ValueError("No session available to decode")
|
||||
|
||||
val = self.session._trace_log.decode(encoding, errors=errors)
|
||||
|
||||
|
|
@ -404,10 +403,10 @@ class SysCommand:
|
|||
|
||||
def output(self, remove_cr: bool = True) -> bytes:
|
||||
if not self.session:
|
||||
raise ValueError('No session available')
|
||||
raise ValueError("No session available")
|
||||
|
||||
if remove_cr:
|
||||
return self.session._trace_log.replace(b'\r\n', b'\n')
|
||||
return self.session._trace_log.replace(b"\r\n", b"\n")
|
||||
|
||||
return self.session._trace_log
|
||||
|
||||
|
|
@ -454,12 +453,12 @@ def run(
|
|||
input=input_data,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=True
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def _pid_exists(pid: int) -> bool:
|
||||
try:
|
||||
return any(subprocess.check_output(['ps', '--no-headers', '-o', 'pid', '-p', str(pid)]).strip())
|
||||
return any(subprocess.check_output(["ps", "--no-headers", "-o", "pid", "-p", str(pid)]).strip())
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
self._item_group = MenuItemGroup(
|
||||
menu_optioons,
|
||||
sort_items=False,
|
||||
checkmarks=True
|
||||
checkmarks=True,
|
||||
)
|
||||
|
||||
super().__init__(self._item_group, config=arch_config)
|
||||
|
|
@ -62,152 +62,152 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
def _get_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=str(_('Archinstall language')),
|
||||
text=str(_("Archinstall language")),
|
||||
action=self._select_archinstall_language,
|
||||
display_action=lambda x: x.display_name if x else '',
|
||||
key='archinstall_language'
|
||||
display_action=lambda x: x.display_name if x else "",
|
||||
key="archinstall_language",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Locales')),
|
||||
text=str(_("Locales")),
|
||||
action=self._locale_selection,
|
||||
preview_action=self._prev_locale,
|
||||
key='locale_config'
|
||||
key="locale_config",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Mirrors and repositories')),
|
||||
text=str(_("Mirrors and repositories")),
|
||||
action=self._mirror_configuration,
|
||||
preview_action=self._prev_mirror_config,
|
||||
key='mirror_config'
|
||||
key="mirror_config",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Disk configuration')),
|
||||
text=str(_("Disk configuration")),
|
||||
action=self._select_disk_config,
|
||||
preview_action=self._prev_disk_config,
|
||||
mandatory=True,
|
||||
key='disk_config'
|
||||
key="disk_config",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Disk encryption')),
|
||||
text=str(_("Disk encryption")),
|
||||
action=self._disk_encryption,
|
||||
preview_action=self._prev_disk_encryption,
|
||||
dependencies=['disk_config'],
|
||||
key='disk_encryption'
|
||||
dependencies=["disk_config"],
|
||||
key="disk_encryption",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Swap')),
|
||||
text=str(_("Swap")),
|
||||
value=True,
|
||||
action=ask_for_swap,
|
||||
preview_action=self._prev_swap,
|
||||
key='swap',
|
||||
key="swap",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Bootloader')),
|
||||
text=str(_("Bootloader")),
|
||||
value=Bootloader.get_default(),
|
||||
action=self._select_bootloader,
|
||||
preview_action=self._prev_bootloader,
|
||||
mandatory=True,
|
||||
key='bootloader',
|
||||
key="bootloader",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Unified kernel images')),
|
||||
text=str(_("Unified kernel images")),
|
||||
value=False,
|
||||
enabled=SysInfo.has_uefi(),
|
||||
action=ask_for_uki,
|
||||
preview_action=self._prev_uki,
|
||||
key='uki',
|
||||
key="uki",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Hostname')),
|
||||
value='archlinux',
|
||||
text=str(_("Hostname")),
|
||||
value="archlinux",
|
||||
action=ask_hostname,
|
||||
preview_action=self._prev_hostname,
|
||||
key='hostname',
|
||||
key="hostname",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Root password')),
|
||||
text=str(_("Root password")),
|
||||
action=self._set_root_password,
|
||||
preview_action=self._prev_root_pwd,
|
||||
key='root_enc_password',
|
||||
key="root_enc_password",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('User account')),
|
||||
text=str(_("User account")),
|
||||
action=self._create_user_account,
|
||||
preview_action=self._prev_users,
|
||||
key='users'
|
||||
key="users",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Profile')),
|
||||
text=str(_("Profile")),
|
||||
action=self._select_profile,
|
||||
preview_action=self._prev_profile,
|
||||
key='profile_config'
|
||||
key="profile_config",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Audio')),
|
||||
text=str(_("Audio")),
|
||||
action=ask_for_audio_selection,
|
||||
preview_action=self._prev_audio,
|
||||
key='audio_config'
|
||||
key="audio_config",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Kernels')),
|
||||
value=['linux'],
|
||||
text=str(_("Kernels")),
|
||||
value=["linux"],
|
||||
action=select_kernel,
|
||||
preview_action=self._prev_kernel,
|
||||
mandatory=True,
|
||||
key='kernels'
|
||||
key="kernels",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Network configuration')),
|
||||
text=str(_("Network configuration")),
|
||||
action=ask_to_configure_network,
|
||||
value={},
|
||||
preview_action=self._prev_network_config,
|
||||
key='network_config'
|
||||
key="network_config",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Parallel Downloads')),
|
||||
text=str(_("Parallel Downloads")),
|
||||
action=add_number_of_parallel_downloads,
|
||||
value=0,
|
||||
preview_action=self._prev_parallel_dw,
|
||||
key='parallel_downloads'
|
||||
key="parallel_downloads",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Additional packages')),
|
||||
text=str(_("Additional packages")),
|
||||
action=self._select_additional_packages,
|
||||
value=[],
|
||||
preview_action=self._prev_additional_pkgs,
|
||||
key='packages'
|
||||
key="packages",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Timezone')),
|
||||
text=str(_("Timezone")),
|
||||
action=ask_for_a_timezone,
|
||||
value='UTC',
|
||||
value="UTC",
|
||||
preview_action=self._prev_tz,
|
||||
key='timezone'
|
||||
key="timezone",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Automatic time sync (NTP)')),
|
||||
text=str(_("Automatic time sync (NTP)")),
|
||||
action=ask_ntp,
|
||||
value=True,
|
||||
preview_action=self._prev_ntp,
|
||||
key='ntp'
|
||||
key="ntp",
|
||||
),
|
||||
MenuItem(
|
||||
text=''
|
||||
text="",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Save configuration')),
|
||||
text=str(_("Save configuration")),
|
||||
action=lambda x: self._safe_config(),
|
||||
key=f'{CONFIG_KEY}_save'
|
||||
key=f"{CONFIG_KEY}_save",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Install')),
|
||||
text=str(_("Install")),
|
||||
preview_action=self._prev_install_invalid_config,
|
||||
key=f'{CONFIG_KEY}_install'
|
||||
key=f"{CONFIG_KEY}_install",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Abort')),
|
||||
text=str(_("Abort")),
|
||||
action=lambda x: exit(1),
|
||||
key=f'{CONFIG_KEY}_abort'
|
||||
)
|
||||
key=f"{CONFIG_KEY}_abort",
|
||||
),
|
||||
]
|
||||
|
||||
def _safe_config(self) -> None:
|
||||
|
|
@ -225,7 +225,7 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
return item.has_value()
|
||||
|
||||
def has_superuser() -> bool:
|
||||
item = self._item_group.find_by_key('users')
|
||||
item = self._item_group.find_by_key("users")
|
||||
|
||||
if item.has_value():
|
||||
users = item.value
|
||||
|
|
@ -236,10 +236,10 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
missing = set()
|
||||
|
||||
for item in self._item_group.items:
|
||||
if item.key in ['root_enc_password', 'users']:
|
||||
if not check('root_enc_password') and not has_superuser():
|
||||
if item.key in ["root_enc_password", "users"]:
|
||||
if not check("root_enc_password") and not has_superuser():
|
||||
missing.add(
|
||||
str(_('Either root-password or at least 1 user with sudo privileges must be specified'))
|
||||
str(_("Either root-password or at least 1 user with sudo privileges must be specified")),
|
||||
)
|
||||
elif item.mandatory:
|
||||
if not check(item.key):
|
||||
|
|
@ -258,6 +258,7 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
|
||||
def _select_archinstall_language(self, preset: Language) -> Language:
|
||||
from .interactions.general_conf import select_archinstall_language
|
||||
|
||||
language = select_archinstall_language(translation_handler.translated_languages, preset)
|
||||
translation_handler.activate(language)
|
||||
|
||||
|
|
@ -277,11 +278,11 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
self._item_group.find_by_key(o.key).text = o.text
|
||||
|
||||
def _disk_encryption(self, preset: DiskEncryption | None) -> DiskEncryption | None:
|
||||
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
|
||||
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key("disk_config").value
|
||||
|
||||
if not disk_config:
|
||||
# this should not happen as the encryption menu has the disk_config as dependency
|
||||
raise ValueError('No disk layout specified')
|
||||
raise ValueError("No disk layout specified")
|
||||
|
||||
if not DiskEncryption.validate_enc(disk_config):
|
||||
return None
|
||||
|
|
@ -306,26 +307,26 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
if network_config.type == NicType.MANUAL:
|
||||
output = FormattedOutput.as_table(network_config.nics)
|
||||
else:
|
||||
output = f'{_('Network configuration')}:\n{network_config.type.display_msg()}'
|
||||
output = f"{_('Network configuration')}:\n{network_config.type.display_msg()}"
|
||||
|
||||
return output
|
||||
return None
|
||||
|
||||
def _prev_additional_pkgs(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
output = '\n'.join(sorted(item.value))
|
||||
output = "\n".join(sorted(item.value))
|
||||
return output
|
||||
return None
|
||||
|
||||
def _prev_tz(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
return f'{_("Timezone")}: {item.value}'
|
||||
return f"{_('Timezone')}: {item.value}"
|
||||
return None
|
||||
|
||||
def _prev_ntp(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
output = f'{_("NTP")}: '
|
||||
output += str(_('Enabled')) if item.value else str(_('Disabled'))
|
||||
output = f"{_('NTP')}: "
|
||||
output += str(_("Enabled")) if item.value else str(_("Disabled"))
|
||||
return output
|
||||
return None
|
||||
|
||||
|
|
@ -333,13 +334,13 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
disk_layout_conf: DiskLayoutConfiguration | None = item.value
|
||||
|
||||
if disk_layout_conf:
|
||||
output = str(_('Configuration type: {}')).format(disk_layout_conf.config_type.display_msg()) + '\n'
|
||||
output = str(_("Configuration type: {}")).format(disk_layout_conf.config_type.display_msg()) + "\n"
|
||||
|
||||
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
|
||||
output += str(_('Mountpoint')) + ': ' + str(disk_layout_conf.mountpoint)
|
||||
output += str(_("Mountpoint")) + ": " + str(disk_layout_conf.mountpoint)
|
||||
|
||||
if disk_layout_conf.lvm_config:
|
||||
output += '{}: {}'.format(str(_('LVM configuration type')), disk_layout_conf.lvm_config.config_type.display_msg())
|
||||
output += "{}: {}".format(str(_("LVM configuration type")), disk_layout_conf.lvm_config.config_type.display_msg())
|
||||
|
||||
return output
|
||||
|
||||
|
|
@ -347,72 +348,72 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
|
||||
def _prev_swap(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
output = f'{_("Swap on zram")}: '
|
||||
output += str(_('Enabled')) if item.value else str(_('Disabled'))
|
||||
output = f"{_('Swap on zram')}: "
|
||||
output += str(_("Enabled")) if item.value else str(_("Disabled"))
|
||||
return output
|
||||
return None
|
||||
|
||||
def _prev_uki(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
output = f'{_('Unified kernel images')}: '
|
||||
output += str(_('Enabled')) if item.value else str(_('Disabled'))
|
||||
output = f"{_('Unified kernel images')}: "
|
||||
output += str(_("Enabled")) if item.value else str(_("Disabled"))
|
||||
return output
|
||||
return None
|
||||
|
||||
def _prev_hostname(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
return f'{_("Hostname")}: {item.value}'
|
||||
return f"{_('Hostname')}: {item.value}"
|
||||
return None
|
||||
|
||||
def _prev_root_pwd(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
password: Password = item.value
|
||||
return f'{_("Root password")}: {password.hidden()}'
|
||||
return f"{_('Root password')}: {password.hidden()}"
|
||||
return None
|
||||
|
||||
def _prev_audio(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
config: AudioConfiguration = item.value
|
||||
return f'{_("Audio")}: {config.audio.value}'
|
||||
return f"{_('Audio')}: {config.audio.value}"
|
||||
return None
|
||||
|
||||
def _prev_parallel_dw(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
return f'{_("Parallel Downloads")}: {item.value}'
|
||||
return f"{_('Parallel Downloads')}: {item.value}"
|
||||
return None
|
||||
|
||||
def _prev_kernel(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
kernel = ', '.join(item.value)
|
||||
return f'{_("Kernel")}: {kernel}'
|
||||
kernel = ", ".join(item.value)
|
||||
return f"{_('Kernel')}: {kernel}"
|
||||
return None
|
||||
|
||||
def _prev_bootloader(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
return f'{_("Bootloader")}: {item.value.value}'
|
||||
return f"{_('Bootloader')}: {item.value.value}"
|
||||
return None
|
||||
|
||||
def _prev_disk_encryption(self, item: MenuItem) -> str | None:
|
||||
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
|
||||
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key("disk_config").value
|
||||
enc_config: DiskEncryption | None = item.value
|
||||
|
||||
if disk_config and not DiskEncryption.validate_enc(disk_config):
|
||||
return str(_('LVM disk encryption with more than 2 partitions is currently not supported'))
|
||||
return str(_("LVM disk encryption with more than 2 partitions is currently not supported"))
|
||||
|
||||
if enc_config:
|
||||
enc_type = EncryptionType.type_to_text(enc_config.encryption_type)
|
||||
output = str(_('Encryption type')) + f': {enc_type}\n'
|
||||
output = str(_("Encryption type")) + f": {enc_type}\n"
|
||||
|
||||
if enc_config.encryption_password:
|
||||
output += str(_('Password')) + f': {enc_config.encryption_password.hidden()}\n'
|
||||
output += str(_("Password")) + f": {enc_config.encryption_password.hidden()}\n"
|
||||
|
||||
if enc_config.partitions:
|
||||
output += f'Partitions: {len(enc_config.partitions)} selected\n'
|
||||
output += f"Partitions: {len(enc_config.partitions)} selected\n"
|
||||
elif enc_config.lvm_volumes:
|
||||
output += f'LVM volumes: {len(enc_config.lvm_volumes)} selected\n'
|
||||
output += f"LVM volumes: {len(enc_config.lvm_volumes)} selected\n"
|
||||
|
||||
if enc_config.hsm_device:
|
||||
output += f'HSM: {enc_config.hsm_device.manufacturer}'
|
||||
output += f"HSM: {enc_config.hsm_device.manufacturer}"
|
||||
|
||||
return output
|
||||
|
||||
|
|
@ -429,12 +430,12 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
XXX: The caller is responsible for wrapping the string with the translation
|
||||
shim if necessary.
|
||||
"""
|
||||
bootloader = self._item_group.find_by_key('bootloader').value
|
||||
bootloader = self._item_group.find_by_key("bootloader").value
|
||||
root_partition: PartitionModification | None = None
|
||||
boot_partition: PartitionModification | None = None
|
||||
efi_partition: PartitionModification | None = None
|
||||
|
||||
if disk_config := self._item_group.find_by_key('disk_config').value:
|
||||
if disk_config := self._item_group.find_by_key("disk_config").value:
|
||||
for layout in disk_config.device_modifications:
|
||||
if root_partition := layout.get_root_partition():
|
||||
break
|
||||
|
|
@ -469,9 +470,9 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
|
||||
def _prev_install_invalid_config(self, item: MenuItem) -> str | None:
|
||||
if missing := self._missing_configs():
|
||||
text = str(_('Missing configurations:\n'))
|
||||
text = str(_("Missing configurations:\n"))
|
||||
for m in missing:
|
||||
text += f'- {m}\n'
|
||||
text += f"- {m}\n"
|
||||
return text[:-1] # remove last new line
|
||||
|
||||
if error := self._validate_bootloader():
|
||||
|
|
@ -490,34 +491,34 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
profile_config: ProfileConfiguration | None = item.value
|
||||
|
||||
if profile_config and profile_config.profile:
|
||||
output = str(_('Profiles')) + ': '
|
||||
output = str(_("Profiles")) + ": "
|
||||
if profile_names := profile_config.profile.current_selection_names():
|
||||
output += ', '.join(profile_names) + '\n'
|
||||
output += ", ".join(profile_names) + "\n"
|
||||
else:
|
||||
output += profile_config.profile.name + '\n'
|
||||
output += profile_config.profile.name + "\n"
|
||||
|
||||
if profile_config.gfx_driver:
|
||||
output += str(_('Graphics driver')) + ': ' + profile_config.gfx_driver.value + '\n'
|
||||
output += str(_("Graphics driver")) + ": " + profile_config.gfx_driver.value + "\n"
|
||||
|
||||
if profile_config.greeter:
|
||||
output += str(_('Greeter')) + ': ' + profile_config.greeter.value + '\n'
|
||||
output += str(_("Greeter")) + ": " + profile_config.greeter.value + "\n"
|
||||
|
||||
return output
|
||||
|
||||
return None
|
||||
|
||||
def _set_root_password(self, preset: str | None = None) -> Password | None:
|
||||
password = get_password(text=str(_('Root password')), allow_skip=True)
|
||||
password = get_password(text=str(_("Root password")), allow_skip=True)
|
||||
return password
|
||||
|
||||
def _select_disk_config(
|
||||
self,
|
||||
preset: DiskLayoutConfiguration | None = None
|
||||
preset: DiskLayoutConfiguration | None = None,
|
||||
) -> DiskLayoutConfiguration | None:
|
||||
disk_config = DiskLayoutConfigurationMenu(preset).run()
|
||||
|
||||
if disk_config != preset:
|
||||
self._menu_item_group.find_by_key('disk_encryption').value = None
|
||||
self._menu_item_group.find_by_key("disk_encryption").value = None
|
||||
|
||||
return disk_config
|
||||
|
||||
|
|
@ -525,7 +526,7 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
bootloader = ask_for_bootloader(preset)
|
||||
|
||||
if bootloader:
|
||||
uki = self._item_group.find_by_key('uki')
|
||||
uki = self._item_group.find_by_key("uki")
|
||||
if not SysInfo.has_uefi() or not bootloader.has_uki_support():
|
||||
uki.value = False
|
||||
uki.enabled = False
|
||||
|
|
@ -536,11 +537,12 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
|
||||
def _select_profile(self, current_profile: ProfileConfiguration | None):
|
||||
from .profile.profile_menu import ProfileMenu
|
||||
|
||||
profile_config = ProfileMenu(preset=current_profile).run()
|
||||
return profile_config
|
||||
|
||||
def _select_additional_packages(self, preset: list[str]) -> list[str]:
|
||||
config: MirrorConfiguration | None = self._item_group.find_by_key('mirror_config').value
|
||||
config: MirrorConfiguration | None = self._item_group.find_by_key("mirror_config").value
|
||||
|
||||
repositories: set[Repository] = set()
|
||||
if config:
|
||||
|
|
@ -548,7 +550,7 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
|
||||
packages = ask_additional_packages_to_install(
|
||||
preset,
|
||||
repositories=repositories
|
||||
repositories=repositories,
|
||||
)
|
||||
|
||||
return packages
|
||||
|
|
@ -579,28 +581,28 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
|
||||
mirror_config: MirrorConfiguration = item.value
|
||||
|
||||
output = ''
|
||||
output = ""
|
||||
if mirror_config.mirror_regions:
|
||||
title = str(_('Selected mirror regions'))
|
||||
divider = '-' * len(title)
|
||||
title = str(_("Selected mirror regions"))
|
||||
divider = "-" * len(title)
|
||||
regions = mirror_config.region_names
|
||||
output += f'{title}\n{divider}\n{regions}\n\n'
|
||||
output += f"{title}\n{divider}\n{regions}\n\n"
|
||||
|
||||
if mirror_config.custom_servers:
|
||||
title = str(_('Custom servers'))
|
||||
divider = '-' * len(title)
|
||||
title = str(_("Custom servers"))
|
||||
divider = "-" * len(title)
|
||||
servers = mirror_config.custom_server_urls
|
||||
output += f'{title}\n{divider}\n{servers}\n\n'
|
||||
output += f"{title}\n{divider}\n{servers}\n\n"
|
||||
|
||||
if mirror_config.optional_repositories:
|
||||
title = str(_('Optional repositories'))
|
||||
divider = '-' * len(title)
|
||||
repos = ', '.join([r.value for r in mirror_config.optional_repositories])
|
||||
output += f'{title}\n{divider}\n{repos}\n\n'
|
||||
title = str(_("Optional repositories"))
|
||||
divider = "-" * len(title)
|
||||
repos = ", ".join([r.value for r in mirror_config.optional_repositories])
|
||||
output += f"{title}\n{divider}\n{repos}\n\n"
|
||||
|
||||
if mirror_config.custom_repositories:
|
||||
title = str(_('Custom repositories'))
|
||||
title = str(_("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()
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class CpuVendor(Enum):
|
||||
AuthenticAMD = 'amd'
|
||||
GenuineIntel = 'intel'
|
||||
_Unknown = 'unknown'
|
||||
AuthenticAMD = "amd"
|
||||
GenuineIntel = "intel"
|
||||
_Unknown = "unknown"
|
||||
|
||||
@classmethod
|
||||
def get_vendor(cls, name: str) -> 'CpuVendor':
|
||||
def get_vendor(cls, name: str) -> "CpuVendor":
|
||||
if vendor := getattr(cls, name, None):
|
||||
return vendor
|
||||
else:
|
||||
|
|
@ -39,54 +39,52 @@ class CpuVendor(Enum):
|
|||
|
||||
def get_ucode(self) -> Path | None:
|
||||
if self._has_microcode():
|
||||
return Path(self.value + '-ucode.img')
|
||||
return Path(self.value + "-ucode.img")
|
||||
return None
|
||||
|
||||
|
||||
class GfxPackage(Enum):
|
||||
Dkms = 'dkms'
|
||||
IntelMediaDriver = 'intel-media-driver'
|
||||
LibvaIntelDriver = 'libva-intel-driver'
|
||||
LibvaMesaDriver = 'libva-mesa-driver'
|
||||
LibvaNvidiaDriver = 'libva-nvidia-driver'
|
||||
Dkms = "dkms"
|
||||
IntelMediaDriver = "intel-media-driver"
|
||||
LibvaIntelDriver = "libva-intel-driver"
|
||||
LibvaMesaDriver = "libva-mesa-driver"
|
||||
LibvaNvidiaDriver = "libva-nvidia-driver"
|
||||
Mesa = "mesa"
|
||||
NvidiaDkms = 'nvidia-dkms'
|
||||
NvidiaOpenDkms = 'nvidia-open-dkms'
|
||||
VulkanIntel = 'vulkan-intel'
|
||||
VulkanRadeon = 'vulkan-radeon'
|
||||
VulkanNouveau = 'vulkan-nouveau'
|
||||
NvidiaDkms = "nvidia-dkms"
|
||||
NvidiaOpenDkms = "nvidia-open-dkms"
|
||||
VulkanIntel = "vulkan-intel"
|
||||
VulkanRadeon = "vulkan-radeon"
|
||||
VulkanNouveau = "vulkan-nouveau"
|
||||
Xf86VideoAmdgpu = "xf86-video-amdgpu"
|
||||
Xf86VideoAti = "xf86-video-ati"
|
||||
Xf86VideoNouveau = 'xf86-video-nouveau'
|
||||
Xf86VideoVmware = 'xf86-video-vmware'
|
||||
XorgServer = 'xorg-server'
|
||||
XorgXinit = 'xorg-xinit'
|
||||
Xf86VideoNouveau = "xf86-video-nouveau"
|
||||
Xf86VideoVmware = "xf86-video-vmware"
|
||||
XorgServer = "xorg-server"
|
||||
XorgXinit = "xorg-xinit"
|
||||
|
||||
|
||||
class GfxDriver(Enum):
|
||||
AllOpenSource = 'All open-source'
|
||||
AmdOpenSource = 'AMD / ATI (open-source)'
|
||||
IntelOpenSource = 'Intel (open-source)'
|
||||
NvidiaOpenKernel = 'Nvidia (open kernel module for newer GPUs, Turing+)'
|
||||
NvidiaOpenSource = 'Nvidia (open-source nouveau driver)'
|
||||
NvidiaProprietary = 'Nvidia (proprietary)'
|
||||
VMOpenSource = 'VMware / VirtualBox (open-source)'
|
||||
AllOpenSource = "All open-source"
|
||||
AmdOpenSource = "AMD / ATI (open-source)"
|
||||
IntelOpenSource = "Intel (open-source)"
|
||||
NvidiaOpenKernel = "Nvidia (open kernel module for newer GPUs, Turing+)"
|
||||
NvidiaOpenSource = "Nvidia (open-source nouveau driver)"
|
||||
NvidiaProprietary = "Nvidia (proprietary)"
|
||||
VMOpenSource = "VMware / VirtualBox (open-source)"
|
||||
|
||||
def is_nvidia(self) -> bool:
|
||||
match self:
|
||||
case GfxDriver.NvidiaProprietary | \
|
||||
GfxDriver.NvidiaOpenSource | \
|
||||
GfxDriver.NvidiaOpenKernel:
|
||||
case GfxDriver.NvidiaProprietary | GfxDriver.NvidiaOpenSource | GfxDriver.NvidiaOpenKernel:
|
||||
return True
|
||||
case _:
|
||||
return False
|
||||
|
||||
def packages_text(self) -> str:
|
||||
pkg_names = [p.value for p in self.gfx_packages()]
|
||||
text = str(_('Installed packages')) + ':\n'
|
||||
text = str(_("Installed packages")) + ":\n"
|
||||
|
||||
for p in sorted(pkg_names):
|
||||
text += f'\t- {p}\n'
|
||||
text += f"\t- {p}\n"
|
||||
|
||||
return text
|
||||
|
||||
|
|
@ -106,7 +104,7 @@ class GfxDriver(Enum):
|
|||
GfxPackage.IntelMediaDriver,
|
||||
GfxPackage.VulkanRadeon,
|
||||
GfxPackage.VulkanIntel,
|
||||
GfxPackage.VulkanNouveau
|
||||
GfxPackage.VulkanNouveau,
|
||||
]
|
||||
case GfxDriver.AmdOpenSource:
|
||||
packages += [
|
||||
|
|
@ -114,38 +112,38 @@ class GfxDriver(Enum):
|
|||
GfxPackage.Xf86VideoAmdgpu,
|
||||
GfxPackage.Xf86VideoAti,
|
||||
GfxPackage.LibvaMesaDriver,
|
||||
GfxPackage.VulkanRadeon
|
||||
GfxPackage.VulkanRadeon,
|
||||
]
|
||||
case GfxDriver.IntelOpenSource:
|
||||
packages += [
|
||||
GfxPackage.Mesa,
|
||||
GfxPackage.LibvaIntelDriver,
|
||||
GfxPackage.IntelMediaDriver,
|
||||
GfxPackage.VulkanIntel
|
||||
GfxPackage.VulkanIntel,
|
||||
]
|
||||
case GfxDriver.NvidiaOpenKernel:
|
||||
packages += [
|
||||
GfxPackage.NvidiaOpenDkms,
|
||||
GfxPackage.Dkms,
|
||||
GfxPackage.LibvaNvidiaDriver
|
||||
GfxPackage.LibvaNvidiaDriver,
|
||||
]
|
||||
case GfxDriver.NvidiaOpenSource:
|
||||
packages += [
|
||||
GfxPackage.Mesa,
|
||||
GfxPackage.Xf86VideoNouveau,
|
||||
GfxPackage.LibvaMesaDriver,
|
||||
GfxPackage.VulkanNouveau
|
||||
GfxPackage.VulkanNouveau,
|
||||
]
|
||||
case GfxDriver.NvidiaProprietary:
|
||||
packages += [
|
||||
GfxPackage.NvidiaDkms,
|
||||
GfxPackage.Dkms,
|
||||
GfxPackage.LibvaNvidiaDriver
|
||||
GfxPackage.LibvaNvidiaDriver,
|
||||
]
|
||||
case GfxDriver.VMOpenSource:
|
||||
packages += [
|
||||
GfxPackage.Mesa,
|
||||
GfxPackage.Xf86VideoVmware
|
||||
GfxPackage.Xf86VideoVmware,
|
||||
]
|
||||
|
||||
return packages
|
||||
|
|
@ -181,7 +179,7 @@ class _SysInfo:
|
|||
|
||||
with mem_info_path.open() as file:
|
||||
for line in file:
|
||||
key, value = line.strip().split(':')
|
||||
key, value = line.strip().split(":")
|
||||
num = value.split()[0]
|
||||
mem_info[key] = int(num)
|
||||
|
||||
|
|
@ -195,7 +193,7 @@ class _SysInfo:
|
|||
"""
|
||||
Returns loaded kernel modules
|
||||
"""
|
||||
modules_path = Path('/proc/modules')
|
||||
modules_path = Path("/proc/modules")
|
||||
modules: list[str] = []
|
||||
|
||||
with modules_path.open() as file:
|
||||
|
|
@ -213,42 +211,42 @@ class SysInfo:
|
|||
@staticmethod
|
||||
def has_wifi() -> bool:
|
||||
ifaces = list(list_interfaces().values())
|
||||
return 'WIRELESS' in enrich_iface_types(ifaces).values()
|
||||
return "WIRELESS" in enrich_iface_types(ifaces).values()
|
||||
|
||||
@staticmethod
|
||||
def has_uefi() -> bool:
|
||||
return os.path.isdir('/sys/firmware/efi')
|
||||
return os.path.isdir("/sys/firmware/efi")
|
||||
|
||||
@staticmethod
|
||||
def _graphics_devices() -> dict[str, str]:
|
||||
cards: dict[str, str] = {}
|
||||
for line in SysCommand("lspci"):
|
||||
if b' VGA ' in line or b' 3D ' in line:
|
||||
_, identifier = line.split(b': ', 1)
|
||||
cards[identifier.strip().decode('UTF-8')] = str(line)
|
||||
if b" VGA " in line or b" 3D " in line:
|
||||
_, identifier = line.split(b": ", 1)
|
||||
cards[identifier.strip().decode("UTF-8")] = str(line)
|
||||
return cards
|
||||
|
||||
@staticmethod
|
||||
def has_nvidia_graphics() -> bool:
|
||||
return any('nvidia' in x.lower() for x in SysInfo._graphics_devices())
|
||||
return any("nvidia" in x.lower() for x in SysInfo._graphics_devices())
|
||||
|
||||
@staticmethod
|
||||
def has_amd_graphics() -> bool:
|
||||
return any('amd' in x.lower() for x in SysInfo._graphics_devices())
|
||||
return any("amd" in x.lower() for x in SysInfo._graphics_devices())
|
||||
|
||||
@staticmethod
|
||||
def has_intel_graphics() -> bool:
|
||||
return any('intel' in x.lower() for x in SysInfo._graphics_devices())
|
||||
return any("intel" in x.lower() for x in SysInfo._graphics_devices())
|
||||
|
||||
@staticmethod
|
||||
def cpu_vendor() -> CpuVendor | None:
|
||||
if vendor := _sys_info.cpu_info.get('vendor_id'):
|
||||
if vendor := _sys_info.cpu_info.get("vendor_id"):
|
||||
return CpuVendor.get_vendor(vendor)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def cpu_model() -> str | None:
|
||||
return _sys_info.cpu_info.get('model name', None)
|
||||
return _sys_info.cpu_info.get("model name", None)
|
||||
|
||||
@staticmethod
|
||||
def sys_vendor() -> str:
|
||||
|
|
@ -262,20 +260,20 @@ class SysInfo:
|
|||
|
||||
@staticmethod
|
||||
def mem_available() -> int:
|
||||
return _sys_info.mem_info_by_key('MemAvailable')
|
||||
return _sys_info.mem_info_by_key("MemAvailable")
|
||||
|
||||
@staticmethod
|
||||
def mem_free() -> int:
|
||||
return _sys_info.mem_info_by_key('MemFree')
|
||||
return _sys_info.mem_info_by_key("MemFree")
|
||||
|
||||
@staticmethod
|
||||
def mem_total() -> int:
|
||||
return _sys_info.mem_info_by_key('MemTotal')
|
||||
return _sys_info.mem_info_by_key("MemTotal")
|
||||
|
||||
@staticmethod
|
||||
def virtualization() -> str | None:
|
||||
try:
|
||||
return str(SysCommand("systemd-detect-virt")).strip('\r\n')
|
||||
return str(SysCommand("systemd-detect-virt")).strip("\r\n")
|
||||
except SysCallError as err:
|
||||
debug(f"Could not detect virtual system: {err}")
|
||||
|
||||
|
|
@ -293,33 +291,33 @@ class SysInfo:
|
|||
|
||||
@staticmethod
|
||||
def requires_sof_fw() -> bool:
|
||||
return 'snd_sof' in _sys_info.loaded_modules
|
||||
return "snd_sof" in _sys_info.loaded_modules
|
||||
|
||||
@staticmethod
|
||||
def requires_alsa_fw() -> bool:
|
||||
modules = (
|
||||
'snd_asihpi',
|
||||
'snd_cs46xx',
|
||||
'snd_darla20',
|
||||
'snd_darla24',
|
||||
'snd_echo3g',
|
||||
'snd_emu10k1',
|
||||
'snd_gina20',
|
||||
'snd_gina24',
|
||||
'snd_hda_codec_ca0132',
|
||||
'snd_hdsp',
|
||||
'snd_indigo',
|
||||
'snd_indigodj',
|
||||
'snd_indigodjx',
|
||||
'snd_indigoio',
|
||||
'snd_indigoiox',
|
||||
'snd_layla20',
|
||||
'snd_layla24',
|
||||
'snd_mia',
|
||||
'snd_mixart',
|
||||
'snd_mona',
|
||||
'snd_pcxhr',
|
||||
'snd_vx_lib'
|
||||
"snd_asihpi",
|
||||
"snd_cs46xx",
|
||||
"snd_darla20",
|
||||
"snd_darla24",
|
||||
"snd_echo3g",
|
||||
"snd_emu10k1",
|
||||
"snd_gina20",
|
||||
"snd_gina24",
|
||||
"snd_hda_codec_ca0132",
|
||||
"snd_hdsp",
|
||||
"snd_indigo",
|
||||
"snd_indigodj",
|
||||
"snd_indigodjx",
|
||||
"snd_indigoio",
|
||||
"snd_indigoiox",
|
||||
"snd_layla20",
|
||||
"snd_layla24",
|
||||
"snd_mia",
|
||||
"snd_mixart",
|
||||
"snd_mona",
|
||||
"snd_pcxhr",
|
||||
"snd_vx_lib",
|
||||
)
|
||||
|
||||
for loaded_module in _sys_info.loaded_modules:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -20,26 +20,26 @@ from .network_menu import ManualNetworkConfig, ask_to_configure_network
|
|||
from .system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_driver, select_kernel
|
||||
|
||||
__all__ = [
|
||||
'ManualNetworkConfig',
|
||||
'UserList',
|
||||
'add_number_of_parallel_downloads',
|
||||
'ask_additional_packages_to_install',
|
||||
'ask_for_a_timezone',
|
||||
'ask_for_additional_users',
|
||||
'ask_for_audio_selection',
|
||||
'ask_for_bootloader',
|
||||
'ask_for_swap',
|
||||
'ask_for_uki',
|
||||
'ask_hostname',
|
||||
'ask_ntp',
|
||||
'ask_to_configure_network',
|
||||
'get_default_partition_layout',
|
||||
'select_archinstall_language',
|
||||
'select_devices',
|
||||
'select_disk_config',
|
||||
'select_driver',
|
||||
'select_kernel',
|
||||
'select_main_filesystem_format',
|
||||
'suggest_multi_disk_layout',
|
||||
'suggest_single_disk_layout',
|
||||
"ManualNetworkConfig",
|
||||
"UserList",
|
||||
"add_number_of_parallel_downloads",
|
||||
"ask_additional_packages_to_install",
|
||||
"ask_for_a_timezone",
|
||||
"ask_for_additional_users",
|
||||
"ask_for_audio_selection",
|
||||
"ask_for_bootloader",
|
||||
"ask_for_swap",
|
||||
"ask_for_uki",
|
||||
"ask_hostname",
|
||||
"ask_ntp",
|
||||
"ask_to_configure_network",
|
||||
"get_default_partition_layout",
|
||||
"select_archinstall_language",
|
||||
"select_devices",
|
||||
"select_disk_config",
|
||||
"select_driver",
|
||||
"select_kernel",
|
||||
"select_main_filesystem_format",
|
||||
"suggest_multi_disk_layout",
|
||||
"suggest_single_disk_layout",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ def select_devices(preset: list[BDevice] | None = []) -> list[BDevice]:
|
|||
search_enabled=False,
|
||||
multi=True,
|
||||
preview_style=PreviewStyle.BOTTOM,
|
||||
preview_size='auto',
|
||||
preview_frame=FrameProperties.max('Partitions'),
|
||||
allow_skip=True
|
||||
preview_size="auto",
|
||||
preview_frame=FrameProperties.max("Partitions"),
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -93,24 +93,24 @@ def select_devices(preset: list[BDevice] | None = []) -> list[BDevice]:
|
|||
|
||||
def get_default_partition_layout(
|
||||
devices: list[BDevice],
|
||||
filesystem_type: FilesystemType | None = None
|
||||
filesystem_type: FilesystemType | None = None,
|
||||
) -> list[DeviceModification]:
|
||||
if len(devices) == 1:
|
||||
device_modification = suggest_single_disk_layout(
|
||||
devices[0],
|
||||
filesystem_type=filesystem_type
|
||||
filesystem_type=filesystem_type,
|
||||
)
|
||||
return [device_modification]
|
||||
else:
|
||||
return suggest_multi_disk_layout(
|
||||
devices,
|
||||
filesystem_type=filesystem_type
|
||||
filesystem_type=filesystem_type,
|
||||
)
|
||||
|
||||
|
||||
def _manual_partitioning(
|
||||
preset: list[DeviceModification],
|
||||
devices: list[BDevice]
|
||||
devices: list[BDevice],
|
||||
) -> list[DeviceModification]:
|
||||
modifications = []
|
||||
for device in devices:
|
||||
|
|
@ -132,7 +132,7 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
|
|||
items = [
|
||||
MenuItem(default_layout, value=default_layout),
|
||||
MenuItem(manual_mode, value=manual_mode),
|
||||
MenuItem(pre_mount_mode, value=pre_mount_mode)
|
||||
MenuItem(pre_mount_mode, value=pre_mount_mode),
|
||||
]
|
||||
group = MenuItemGroup(items, sort_items=False)
|
||||
|
||||
|
|
@ -143,8 +143,8 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
|
|||
group,
|
||||
allow_skip=True,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Disk configuration type'))),
|
||||
allow_reset=True
|
||||
frame=FrameProperties.min(str(_("Disk configuration type"))),
|
||||
allow_reset=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -156,10 +156,10 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
|
|||
selection = result.get_value()
|
||||
|
||||
if selection == pre_mount_mode:
|
||||
output = 'You will use whatever drive-setup is mounted at the specified directory\n'
|
||||
output = "You will use whatever drive-setup is mounted at the specified directory\n"
|
||||
output += "WARNING: Archinstall won't check the suitability of this setup\n"
|
||||
|
||||
path = prompt_dir(str(_('Root mount directory')), output, allow_skip=True)
|
||||
path = prompt_dir(str(_("Root mount directory")), output, allow_skip=True)
|
||||
|
||||
if path is None:
|
||||
return None
|
||||
|
|
@ -169,7 +169,7 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
|
|||
return DiskLayoutConfiguration(
|
||||
config_type=DiskLayoutType.Pre_mount,
|
||||
device_modifications=mods,
|
||||
mountpoint=path
|
||||
mountpoint=path,
|
||||
)
|
||||
|
||||
preset_devices = [mod.device for mod in preset.device_modifications] if preset else []
|
||||
|
|
@ -183,7 +183,7 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
|
|||
if modifications:
|
||||
return DiskLayoutConfiguration(
|
||||
config_type=DiskLayoutType.Default,
|
||||
device_modifications=modifications
|
||||
device_modifications=modifications,
|
||||
)
|
||||
elif result.get_value() == manual_mode:
|
||||
preset_mods = preset.device_modifications if preset else []
|
||||
|
|
@ -192,7 +192,7 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
|
|||
if modifications:
|
||||
return DiskLayoutConfiguration(
|
||||
config_type=DiskLayoutType.Manual,
|
||||
device_modifications=modifications
|
||||
device_modifications=modifications,
|
||||
)
|
||||
|
||||
return None
|
||||
|
|
@ -213,8 +213,8 @@ def select_lvm_config(
|
|||
group,
|
||||
allow_reset=True,
|
||||
allow_skip=True,
|
||||
frame=FrameProperties.min(str(_('LVM configuration type'))),
|
||||
alignment=Alignment.CENTER
|
||||
frame=FrameProperties.min(str(_("LVM configuration type"))),
|
||||
alignment=Alignment.CENTER,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -242,42 +242,42 @@ def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModifi
|
|||
type=PartitionType.Primary,
|
||||
start=start,
|
||||
length=size,
|
||||
mountpoint=Path('/boot'),
|
||||
mountpoint=Path("/boot"),
|
||||
fs_type=FilesystemType.Fat32,
|
||||
flags=flags
|
||||
flags=flags,
|
||||
)
|
||||
|
||||
|
||||
def select_main_filesystem_format() -> FilesystemType:
|
||||
items = [
|
||||
MenuItem('btrfs', value=FilesystemType.Btrfs),
|
||||
MenuItem('ext4', value=FilesystemType.Ext4),
|
||||
MenuItem('xfs', value=FilesystemType.Xfs),
|
||||
MenuItem('f2fs', value=FilesystemType.F2fs)
|
||||
MenuItem("btrfs", value=FilesystemType.Btrfs),
|
||||
MenuItem("ext4", value=FilesystemType.Ext4),
|
||||
MenuItem("xfs", value=FilesystemType.Xfs),
|
||||
MenuItem("f2fs", value=FilesystemType.F2fs),
|
||||
]
|
||||
|
||||
if arch_config_handler.args.advanced:
|
||||
items.append(MenuItem('ntfs', value=FilesystemType.Ntfs))
|
||||
items.append(MenuItem("ntfs", value=FilesystemType.Ntfs))
|
||||
|
||||
group = MenuItemGroup(items, sort_items=False)
|
||||
result = SelectMenu[FilesystemType](
|
||||
group,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min('Filesystem'),
|
||||
allow_skip=False
|
||||
frame=FrameProperties.min("Filesystem"),
|
||||
allow_skip=False,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case _:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
|
||||
def select_mount_options() -> list[str]:
|
||||
prompt = str(_('Would you like to use compression or disable CoW?')) + '\n'
|
||||
compression = str(_('Use compression'))
|
||||
disable_cow = str(_('Disable Copy-on-Write'))
|
||||
prompt = str(_("Would you like to use compression or disable CoW?")) + "\n"
|
||||
compression = str(_("Use compression"))
|
||||
disable_cow = str(_("Disable Copy-on-Write"))
|
||||
|
||||
items = [
|
||||
MenuItem(compression, value=BtrfsMountOption.compress.value),
|
||||
|
|
@ -291,7 +291,7 @@ def select_mount_options() -> list[str]:
|
|||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
search_enabled=False,
|
||||
allow_skip=True
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -300,7 +300,7 @@ def select_mount_options() -> list[str]:
|
|||
case ResultType.Selection:
|
||||
return [result.get_value()]
|
||||
case _:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
|
||||
def process_root_partition_size(total_size: Size, sector_size: SectorSize) -> Size:
|
||||
|
|
@ -321,7 +321,7 @@ def process_root_partition_size(total_size: Size, sector_size: SectorSize) -> Si
|
|||
def suggest_single_disk_layout(
|
||||
device: BDevice,
|
||||
filesystem_type: FilesystemType | None = None,
|
||||
separate_home: bool | None = None
|
||||
separate_home: bool | None = None,
|
||||
) -> DeviceModification:
|
||||
if not filesystem_type:
|
||||
filesystem_type = select_main_filesystem_format()
|
||||
|
|
@ -332,7 +332,7 @@ def suggest_single_disk_layout(
|
|||
min_size_to_allow_home_part = Size(64, Unit.GiB, sector_size)
|
||||
|
||||
if filesystem_type == FilesystemType.Btrfs:
|
||||
prompt = str(_('Would you like to use BTRFS subvolumes with a default structure?')) + '\n'
|
||||
prompt = str(_("Would you like to use BTRFS subvolumes with a default structure?")) + "\n"
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_focus_by_value(MenuItem.yes().value)
|
||||
result = SelectMenu[bool](
|
||||
|
|
@ -341,7 +341,7 @@ def suggest_single_disk_layout(
|
|||
alignment=Alignment.CENTER,
|
||||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
allow_skip=False
|
||||
allow_skip=False,
|
||||
).run()
|
||||
|
||||
using_subvolumes = result.item() == MenuItem.yes()
|
||||
|
|
@ -364,16 +364,12 @@ def suggest_single_disk_layout(
|
|||
boot_partition = _boot_partition(sector_size, using_gpt)
|
||||
device_modification.add_partition(boot_partition)
|
||||
|
||||
if (
|
||||
separate_home is False
|
||||
or using_subvolumes
|
||||
or total_size < min_size_to_allow_home_part
|
||||
):
|
||||
if separate_home is False or using_subvolumes or total_size < min_size_to_allow_home_part:
|
||||
using_home_partition = False
|
||||
elif separate_home:
|
||||
using_home_partition = True
|
||||
else:
|
||||
prompt = str(_('Would you like to create a separate partition for /home?')) + '\n'
|
||||
prompt = str(_("Would you like to create a separate partition for /home?")) + "\n"
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_focus_by_value(MenuItem.yes().value)
|
||||
result = SelectMenu(
|
||||
|
|
@ -382,7 +378,7 @@ def suggest_single_disk_layout(
|
|||
orientation=Orientation.HORIZONTAL,
|
||||
columns=2,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=False
|
||||
allow_skip=False,
|
||||
).run()
|
||||
|
||||
using_home_partition = result.item() == MenuItem.yes()
|
||||
|
|
@ -401,9 +397,9 @@ def suggest_single_disk_layout(
|
|||
type=PartitionType.Primary,
|
||||
start=root_start,
|
||||
length=root_length,
|
||||
mountpoint=Path('/') if not using_subvolumes else None,
|
||||
mountpoint=Path("/") if not using_subvolumes else None,
|
||||
fs_type=filesystem_type,
|
||||
mount_options=mount_options
|
||||
mount_options=mount_options,
|
||||
)
|
||||
|
||||
device_modification.add_partition(root_partition)
|
||||
|
|
@ -413,10 +409,10 @@ def suggest_single_disk_layout(
|
|||
# https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash
|
||||
# https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh
|
||||
subvolumes = [
|
||||
SubvolumeModification(Path('@'), Path('/')),
|
||||
SubvolumeModification(Path('@home'), Path('/home')),
|
||||
SubvolumeModification(Path('@log'), Path('/var/log')),
|
||||
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg'))
|
||||
SubvolumeModification(Path("@"), Path("/")),
|
||||
SubvolumeModification(Path("@home"), Path("/home")),
|
||||
SubvolumeModification(Path("@log"), Path("/var/log")),
|
||||
SubvolumeModification(Path("@pkg"), Path("/var/cache/pacman/pkg")),
|
||||
]
|
||||
root_partition.btrfs_subvols = subvolumes
|
||||
elif using_home_partition:
|
||||
|
|
@ -435,10 +431,10 @@ def suggest_single_disk_layout(
|
|||
type=PartitionType.Primary,
|
||||
start=home_start,
|
||||
length=home_length,
|
||||
mountpoint=Path('/home'),
|
||||
mountpoint=Path("/home"),
|
||||
fs_type=filesystem_type,
|
||||
mount_options=mount_options,
|
||||
flags=flags
|
||||
flags=flags,
|
||||
)
|
||||
device_modification.add_partition(home_partition)
|
||||
|
||||
|
|
@ -447,7 +443,7 @@ def suggest_single_disk_layout(
|
|||
|
||||
def suggest_multi_disk_layout(
|
||||
devices: list[BDevice],
|
||||
filesystem_type: FilesystemType | None = None
|
||||
filesystem_type: FilesystemType | None = None,
|
||||
) -> list[DeviceModification]:
|
||||
if not devices:
|
||||
return []
|
||||
|
|
@ -478,11 +474,11 @@ def suggest_multi_disk_layout(
|
|||
root_device: BDevice | None = sorted_delta[0][0]
|
||||
|
||||
if home_device is None or root_device is None:
|
||||
text = str(_('The selected drives do not have the minimum capacity required for an automatic suggestion\n'))
|
||||
text += str(_('Minimum capacity for /home partition: {}GiB\n').format(min_home_partition_size.format_size(Unit.GiB)))
|
||||
text += str(_('Minimum capacity for Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(Unit.GiB)))
|
||||
text = str(_("The selected drives do not have the minimum capacity required for an automatic suggestion\n"))
|
||||
text += str(_("Minimum capacity for /home partition: {}GiB\n").format(min_home_partition_size.format_size(Unit.GiB)))
|
||||
text += str(_("Minimum capacity for Arch Linux partition: {}GiB").format(desired_root_partition_size.format_size(Unit.GiB)))
|
||||
|
||||
items = [MenuItem(str(_('Continue')))]
|
||||
items = [MenuItem(str(_("Continue")))]
|
||||
group = MenuItemGroup(items)
|
||||
SelectMenu(group).run()
|
||||
|
||||
|
|
@ -491,11 +487,11 @@ def suggest_multi_disk_layout(
|
|||
if filesystem_type == FilesystemType.Btrfs:
|
||||
mount_options = select_mount_options()
|
||||
|
||||
device_paths = ', '.join([str(d.device_info.path) for d in devices])
|
||||
device_paths = ", ".join([str(d.device_info.path) for d in devices])
|
||||
|
||||
debug(f'Suggesting multi-disk-layout for devices: {device_paths}')
|
||||
debug(f'/root: {root_device.device_info.path}')
|
||||
debug(f'/home: {home_device.device_info.path}')
|
||||
debug(f"Suggesting multi-disk-layout for devices: {device_paths}")
|
||||
debug(f"/root: {root_device.device_info.path}")
|
||||
debug(f"/home: {home_device.device_info.path}")
|
||||
|
||||
root_device_modification = DeviceModification(root_device, wipe=True)
|
||||
home_device_modification = DeviceModification(home_device, wipe=True)
|
||||
|
|
@ -523,9 +519,9 @@ def suggest_multi_disk_layout(
|
|||
type=PartitionType.Primary,
|
||||
start=root_start,
|
||||
length=root_length,
|
||||
mountpoint=Path('/'),
|
||||
mountpoint=Path("/"),
|
||||
mount_options=mount_options,
|
||||
fs_type=filesystem_type
|
||||
fs_type=filesystem_type,
|
||||
)
|
||||
root_device_modification.add_partition(root_partition)
|
||||
|
||||
|
|
@ -545,10 +541,10 @@ def suggest_multi_disk_layout(
|
|||
type=PartitionType.Primary,
|
||||
start=home_start,
|
||||
length=home_length,
|
||||
mountpoint=Path('/home'),
|
||||
mountpoint=Path("/home"),
|
||||
mount_options=mount_options,
|
||||
fs_type=filesystem_type,
|
||||
flags=flags
|
||||
flags=flags,
|
||||
)
|
||||
home_device_modification.add_partition(home_partition)
|
||||
|
||||
|
|
@ -558,10 +554,10 @@ def suggest_multi_disk_layout(
|
|||
def suggest_lvm_layout(
|
||||
disk_config: DiskLayoutConfiguration,
|
||||
filesystem_type: FilesystemType | None = None,
|
||||
vg_grp_name: str = 'ArchinstallVg',
|
||||
vg_grp_name: str = "ArchinstallVg",
|
||||
) -> LvmConfiguration:
|
||||
if disk_config.config_type != DiskLayoutType.Default:
|
||||
raise ValueError('LVM suggested volumes are only available for default partitioning')
|
||||
raise ValueError("LVM suggested volumes are only available for default partitioning")
|
||||
|
||||
using_subvolumes = False
|
||||
btrfs_subvols = []
|
||||
|
|
@ -572,7 +568,7 @@ def suggest_lvm_layout(
|
|||
filesystem_type = select_main_filesystem_format()
|
||||
|
||||
if filesystem_type == FilesystemType.Btrfs:
|
||||
prompt = str(_('Would you like to use BTRFS subvolumes with a default structure?')) + '\n'
|
||||
prompt = str(_("Would you like to use BTRFS subvolumes with a default structure?")) + "\n"
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_focus_by_value(MenuItem.yes().value)
|
||||
|
||||
|
|
@ -591,10 +587,10 @@ def suggest_lvm_layout(
|
|||
|
||||
if using_subvolumes:
|
||||
btrfs_subvols = [
|
||||
SubvolumeModification(Path('@'), Path('/')),
|
||||
SubvolumeModification(Path('@home'), Path('/home')),
|
||||
SubvolumeModification(Path('@log'), Path('/var/log')),
|
||||
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg'))
|
||||
SubvolumeModification(Path("@"), Path("/")),
|
||||
SubvolumeModification(Path("@home"), Path("/home")),
|
||||
SubvolumeModification(Path("@log"), Path("/var/log")),
|
||||
SubvolumeModification(Path("@pkg"), Path("/var/cache/pacman/pkg")),
|
||||
]
|
||||
|
||||
home_volume = False
|
||||
|
|
@ -610,7 +606,7 @@ def suggest_lvm_layout(
|
|||
other_part.append(part)
|
||||
|
||||
if not boot_part:
|
||||
raise ValueError('Unable to find boot partition in partition modifications')
|
||||
raise ValueError("Unable to find boot partition in partition modifications")
|
||||
|
||||
total_vol_available = sum(
|
||||
[p.length for p in other_part],
|
||||
|
|
@ -623,12 +619,12 @@ def suggest_lvm_layout(
|
|||
|
||||
root_vol = LvmVolume(
|
||||
status=LvmVolumeStatus.Create,
|
||||
name='root',
|
||||
name="root",
|
||||
fs_type=filesystem_type,
|
||||
length=root_vol_size,
|
||||
mountpoint=Path('/'),
|
||||
mountpoint=Path("/"),
|
||||
btrfs_subvols=btrfs_subvols,
|
||||
mount_options=mount_options
|
||||
mount_options=mount_options,
|
||||
)
|
||||
|
||||
lvm_vol_group.volumes.append(root_vol)
|
||||
|
|
@ -636,10 +632,10 @@ def suggest_lvm_layout(
|
|||
if home_volume:
|
||||
home_vol = LvmVolume(
|
||||
status=LvmVolumeStatus.Create,
|
||||
name='home',
|
||||
name="home",
|
||||
fs_type=filesystem_type,
|
||||
length=home_vol_size,
|
||||
mountpoint=Path('/home'),
|
||||
mountpoint=Path("/home"),
|
||||
)
|
||||
|
||||
lvm_vol_group.volumes.append(home_vol)
|
||||
|
|
|
|||
|
|
@ -26,17 +26,22 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class PostInstallationAction(Enum):
|
||||
EXIT = str(_('Exit archinstall'))
|
||||
REBOOT = str(_('Reboot system'))
|
||||
CHROOT = str(_('chroot into installation for post-installation configurations'))
|
||||
EXIT = str(_("Exit archinstall"))
|
||||
REBOOT = str(_("Reboot system"))
|
||||
CHROOT = str(_("chroot into installation for post-installation configurations"))
|
||||
|
||||
|
||||
def ask_ntp(preset: bool = True) -> bool:
|
||||
header = str(_('Would you like to use automatic time synchronization (NTP) with the default time servers?\n')) + '\n'
|
||||
header += str(_(
|
||||
'Hardware time and other post-configuration steps might be required in order for NTP to work.\n'
|
||||
'For more information, please check the Arch wiki'
|
||||
)) + '\n'
|
||||
header = str(_("Would you like to use automatic time synchronization (NTP) with the default time servers?\n")) + "\n"
|
||||
header += (
|
||||
str(
|
||||
_(
|
||||
"Hardware time and other post-configuration steps might be required in order for NTP to work.\n"
|
||||
"For more information, please check the Arch wiki",
|
||||
)
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
preset_val = MenuItem.yes() if preset else MenuItem.no()
|
||||
group = MenuItemGroup.yes_no()
|
||||
|
|
@ -48,7 +53,7 @@ def ask_ntp(preset: bool = True) -> bool:
|
|||
allow_skip=True,
|
||||
alignment=Alignment.CENTER,
|
||||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -57,15 +62,15 @@ def ask_ntp(preset: bool = True) -> bool:
|
|||
case ResultType.Selection:
|
||||
return result.item() == MenuItem.yes()
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
|
||||
def ask_hostname(preset: str | None = None) -> str | None:
|
||||
result = EditMenu(
|
||||
str(_('Hostname')),
|
||||
str(_("Hostname")),
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True,
|
||||
default_text=preset
|
||||
default_text=preset,
|
||||
).input()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -77,11 +82,11 @@ def ask_hostname(preset: str | None = None) -> str | None:
|
|||
return None
|
||||
return hostname
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
|
||||
def ask_for_a_timezone(preset: str | None = None) -> str | None:
|
||||
default = 'UTC'
|
||||
default = "UTC"
|
||||
timezones = list_timezones()
|
||||
|
||||
items = [MenuItem(tz, value=tz) for tz in timezones]
|
||||
|
|
@ -93,7 +98,7 @@ def ask_for_a_timezone(preset: str | None = None) -> str | None:
|
|||
group,
|
||||
allow_reset=True,
|
||||
allow_skip=True,
|
||||
frame=FrameProperties.min(str(_('Timezone'))),
|
||||
frame=FrameProperties.min(str(_("Timezone"))),
|
||||
alignment=Alignment.CENTER,
|
||||
).run()
|
||||
|
||||
|
|
@ -117,7 +122,7 @@ def ask_for_audio_selection(preset: AudioConfiguration | None = None) -> AudioCo
|
|||
group,
|
||||
allow_skip=True,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Audio')))
|
||||
frame=FrameProperties.min(str(_("Audio"))),
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -126,7 +131,7 @@ def ask_for_audio_selection(preset: AudioConfiguration | None = None) -> AudioCo
|
|||
case ResultType.Selection:
|
||||
return AudioConfiguration(audio=result.get_value())
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
|
||||
def select_language(preset: str | None = None) -> str | None:
|
||||
|
|
@ -137,8 +142,7 @@ def select_language(preset: str | None = None) -> str | None:
|
|||
# raise Deprecated("select_language() has been deprecated, use select_kb_layout() instead.")
|
||||
|
||||
# No need to translate this i feel, as it's a short lived message.
|
||||
warn(
|
||||
"select_language() is deprecated, use select_kb_layout() instead. select_language() will be removed in a future version")
|
||||
warn("select_language() is deprecated, use select_kb_layout() instead. select_language() will be removed in a future version")
|
||||
|
||||
return select_kb_layout(preset)
|
||||
|
||||
|
|
@ -152,9 +156,9 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
|
|||
group = MenuItemGroup(items, sort_items=True)
|
||||
group.set_focus_by_value(preset)
|
||||
|
||||
title = 'NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n'
|
||||
title = "NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n"
|
||||
title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n'
|
||||
title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n'
|
||||
title += "e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n"
|
||||
|
||||
result = SelectMenu[Language](
|
||||
group,
|
||||
|
|
@ -162,7 +166,7 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
|
|||
allow_skip=True,
|
||||
allow_reset=False,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(header=str(_('Select language')))
|
||||
frame=FrameProperties.min(header=str(_("Select language"))),
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -171,27 +175,27 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
|
|||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Language selection not handled')
|
||||
raise ValueError("Language selection not handled")
|
||||
|
||||
|
||||
def ask_additional_packages_to_install(
|
||||
preset: list[str] = [],
|
||||
repositories: set[Repository] = set()
|
||||
repositories: set[Repository] = set(),
|
||||
) -> list[str]:
|
||||
repositories |= {Repository.Core, Repository.Extra}
|
||||
|
||||
respos_text = ', '.join([r.value for r in repositories])
|
||||
output = str(_('Repositories: {}')).format(respos_text) + '\n'
|
||||
respos_text = ", ".join([r.value for r in repositories])
|
||||
output = str(_("Repositories: {}")).format(respos_text) + "\n"
|
||||
|
||||
output += str(_('Loading packages...'))
|
||||
output += str(_("Loading packages..."))
|
||||
Tui.print(output, clear_screen=True)
|
||||
|
||||
packages = list_available_packages(tuple(repositories))
|
||||
package_groups = PackageGroup.from_available_packages(packages)
|
||||
|
||||
# Additional packages (with some light weight error handling for invalid package names)
|
||||
header = str(_('Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.')) + '\n'
|
||||
header += str(_('Select any packages from the below list that should be installed additionally')) + '\n'
|
||||
header = str(_("Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.")) + "\n"
|
||||
header += str(_("Select any packages from the below list that should be installed additionally")) + "\n"
|
||||
|
||||
# there are over 15k packages so this needs to be quick
|
||||
preset_packages: list[AvailablePackage | PackageGroup] = []
|
||||
|
|
@ -205,16 +209,18 @@ def ask_additional_packages_to_install(
|
|||
MenuItem(
|
||||
name,
|
||||
value=pkg,
|
||||
preview_action=lambda x: x.value.info()
|
||||
) for name, pkg in packages.items()
|
||||
preview_action=lambda x: x.value.info(),
|
||||
)
|
||||
for name, pkg in packages.items()
|
||||
]
|
||||
|
||||
items += [
|
||||
MenuItem(
|
||||
name,
|
||||
value=group,
|
||||
preview_action=lambda x: x.value.info()
|
||||
) for name, group in package_groups.items()
|
||||
preview_action=lambda x: x.value.info(),
|
||||
)
|
||||
for name, group in package_groups.items()
|
||||
]
|
||||
|
||||
menu_group = MenuItemGroup(items, sort_items=True)
|
||||
|
|
@ -227,9 +233,9 @@ def ask_additional_packages_to_install(
|
|||
allow_reset=True,
|
||||
allow_skip=True,
|
||||
multi=True,
|
||||
preview_frame=FrameProperties.max('Package info'),
|
||||
preview_frame=FrameProperties.max("Package info"),
|
||||
preview_style=PreviewStyle.RIGHT,
|
||||
preview_size='auto'
|
||||
preview_size="auto",
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -245,10 +251,10 @@ def ask_additional_packages_to_install(
|
|||
def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
|
||||
max_recommended = 5
|
||||
|
||||
header = str(_('This option enables the number of parallel downloads that can occur during package downloads')) + '\n'
|
||||
header += str(_('Enter the number of parallel downloads to be enabled.\n\nNote:\n'))
|
||||
header += str(_(' - Maximum recommended value : {} ( Allows {} parallel downloads at a time )')).format(max_recommended, max_recommended) + '\n'
|
||||
header += str(_(' - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n'))
|
||||
header = str(_("This option enables the number of parallel downloads that can occur during package downloads")) + "\n"
|
||||
header += str(_("Enter the number of parallel downloads to be enabled.\n\nNote:\n"))
|
||||
header += str(_(" - Maximum recommended value : {} ( Allows {} parallel downloads at a time )")).format(max_recommended, max_recommended) + "\n"
|
||||
header += str(_(" - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n"))
|
||||
|
||||
def validator(s: str) -> str | None:
|
||||
try:
|
||||
|
|
@ -258,15 +264,15 @@ def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
return str(_('Invalid download number'))
|
||||
return str(_("Invalid download number"))
|
||||
|
||||
result = EditMenu(
|
||||
str(_('Number downloads')),
|
||||
str(_("Number downloads")),
|
||||
header=header,
|
||||
allow_skip=True,
|
||||
allow_reset=True,
|
||||
validator=validator,
|
||||
default_text=str(preset) if preset is not None else None
|
||||
default_text=str(preset) if preset is not None else None,
|
||||
).input()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -294,8 +300,8 @@ def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
|
|||
|
||||
|
||||
def ask_post_installation() -> PostInstallationAction:
|
||||
header = str(_('Installation completed')) + '\n\n'
|
||||
header += str(_('What would you like to do next?')) + '\n'
|
||||
header = str(_("Installation completed")) + "\n\n"
|
||||
header += str(_("What would you like to do next?")) + "\n"
|
||||
|
||||
items = [MenuItem(action.value, value=action) for action in PostInstallationAction]
|
||||
group = MenuItemGroup(items)
|
||||
|
|
@ -311,11 +317,11 @@ def ask_post_installation() -> PostInstallationAction:
|
|||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case _:
|
||||
raise ValueError('Post installation action not handled')
|
||||
raise ValueError("Post installation action not handled")
|
||||
|
||||
|
||||
def ask_abort() -> None:
|
||||
prompt = str(_('Do you really want to abort?')) + '\n'
|
||||
prompt = str(_("Do you really want to abort?")) + "\n"
|
||||
group = MenuItemGroup.yes_no()
|
||||
|
||||
result = SelectMenu[bool](
|
||||
|
|
@ -324,7 +330,7 @@ def ask_abort() -> None:
|
|||
allow_skip=False,
|
||||
alignment=Alignment.CENTER,
|
||||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
).run()
|
||||
|
||||
if result.item() == MenuItem.yes():
|
||||
|
|
|
|||
|
|
@ -23,17 +23,17 @@ if TYPE_CHECKING:
|
|||
class UserList(ListManager[User]):
|
||||
def __init__(self, prompt: str, lusers: list[User]):
|
||||
self._actions = [
|
||||
str(_('Add a user')),
|
||||
str(_('Change password')),
|
||||
str(_('Promote/Demote user')),
|
||||
str(_('Delete User'))
|
||||
str(_("Add a user")),
|
||||
str(_("Change password")),
|
||||
str(_("Promote/Demote user")),
|
||||
str(_("Delete User")),
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
lusers,
|
||||
[self._actions[0]],
|
||||
self._actions[1:],
|
||||
prompt
|
||||
prompt,
|
||||
)
|
||||
|
||||
@override
|
||||
|
|
@ -50,8 +50,8 @@ class UserList(ListManager[User]):
|
|||
data = [d for d in data if d.username != new_user.username]
|
||||
data += [new_user]
|
||||
elif action == self._actions[1] and entry: # change password
|
||||
header = f'{_("User")}: {entry.username}\n'
|
||||
new_password = get_password(str(_('Password')), header=header)
|
||||
header = f"{_('User')}: {entry.username}\n"
|
||||
new_password = get_password(str(_("Password")), header=header)
|
||||
|
||||
if new_password:
|
||||
user = next(filter(lambda x: x == entry, data))
|
||||
|
|
@ -65,15 +65,15 @@ class UserList(ListManager[User]):
|
|||
return data
|
||||
|
||||
def _check_for_correct_username(self, username: str) -> str | None:
|
||||
if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32:
|
||||
if re.match(r"^[a-z_][a-z0-9_-]*\$?$", username) and len(username) <= 32:
|
||||
return None
|
||||
return str(_("The username you entered is invalid"))
|
||||
|
||||
def _add_user(self) -> User | None:
|
||||
editResult = EditMenu(
|
||||
str(_('Username')),
|
||||
str(_("Username")),
|
||||
allow_skip=True,
|
||||
validator=self._check_for_correct_username
|
||||
validator=self._check_for_correct_username,
|
||||
).input()
|
||||
|
||||
match editResult.type_:
|
||||
|
|
@ -82,16 +82,16 @@ class UserList(ListManager[User]):
|
|||
case ResultType.Selection:
|
||||
username = editResult.text()
|
||||
case _:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
header = f'{_("Username")}: {username}\n'
|
||||
header = f"{_('Username')}: {username}\n"
|
||||
|
||||
password = get_password(str(_('Password')), header=header, allow_skip=True)
|
||||
password = get_password(str(_("Password")), header=header, allow_skip=True)
|
||||
|
||||
if not password:
|
||||
return None
|
||||
|
||||
header += f'{_("Password")}: {password.hidden()}\n\n'
|
||||
header += f"{_('Password')}: {password.hidden()}\n\n"
|
||||
header += str(_('Should "{}" be a superuser (sudo)?\n')).format(username)
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
|
|
@ -104,18 +104,18 @@ class UserList(ListManager[User]):
|
|||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
search_enabled=False,
|
||||
allow_skip=False
|
||||
allow_skip=False,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
sudo = result.item() == MenuItem.yes()
|
||||
case _:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
return User(username, password, sudo)
|
||||
|
||||
|
||||
def ask_for_additional_users(prompt: str = '', defined_users: list[User] = []) -> list[User]:
|
||||
def ask_for_additional_users(prompt: str = "", defined_users: list[User] = []) -> list[User]:
|
||||
users = UserList(prompt, defined_users).run()
|
||||
return users
|
||||
|
|
|
|||
|
|
@ -23,21 +23,21 @@ if TYPE_CHECKING:
|
|||
class ManualNetworkConfig(ListManager[Nic]):
|
||||
def __init__(self, prompt: str, preset: list[Nic]):
|
||||
self._actions = [
|
||||
str(_('Add interface')),
|
||||
str(_('Edit interface')),
|
||||
str(_('Delete interface'))
|
||||
str(_("Add interface")),
|
||||
str(_("Edit interface")),
|
||||
str(_("Delete interface")),
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
preset,
|
||||
[self._actions[0]],
|
||||
self._actions[1:],
|
||||
prompt
|
||||
prompt,
|
||||
)
|
||||
|
||||
@override
|
||||
def selected_action_display(self, selection: Nic) -> str:
|
||||
return selection.iface if selection.iface else ''
|
||||
return selection.iface if selection.iface else ""
|
||||
|
||||
@override
|
||||
def handle_action(self, action: str, entry: Nic | None, data: list[Nic]) -> list[Nic]:
|
||||
|
|
@ -73,8 +73,8 @@ class ManualNetworkConfig(ListManager[Nic]):
|
|||
result = SelectMenu[str](
|
||||
group,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Interfaces'))),
|
||||
allow_skip=True
|
||||
frame=FrameProperties.min(str(_("Interfaces"))),
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -83,7 +83,7 @@ class ManualNetworkConfig(ListManager[Nic]):
|
|||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
def _get_ip_address(
|
||||
self,
|
||||
|
|
@ -91,11 +91,11 @@ class ManualNetworkConfig(ListManager[Nic]):
|
|||
header: str,
|
||||
allow_skip: bool,
|
||||
multi: bool,
|
||||
preset: str | None = None
|
||||
preset: str | None = None,
|
||||
) -> str | None:
|
||||
def validator(ip: str) -> str | None:
|
||||
if multi:
|
||||
ips = ip.split(' ')
|
||||
ips = ip.split(" ")
|
||||
else:
|
||||
ips = [ip]
|
||||
|
||||
|
|
@ -104,14 +104,14 @@ class ManualNetworkConfig(ListManager[Nic]):
|
|||
ipaddress.ip_interface(ip)
|
||||
return None
|
||||
except ValueError:
|
||||
return str(_('You need to enter a valid IP in IP-config mode'))
|
||||
return str(_("You need to enter a valid IP in IP-config mode"))
|
||||
|
||||
result = EditMenu(
|
||||
title,
|
||||
header=header,
|
||||
validator=validator,
|
||||
allow_skip=allow_skip,
|
||||
default_text=preset
|
||||
default_text=preset,
|
||||
).input()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -120,14 +120,14 @@ class ManualNetworkConfig(ListManager[Nic]):
|
|||
case ResultType.Selection:
|
||||
return result.text()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
def _edit_iface(self, edit_nic: Nic) -> Nic:
|
||||
iface_name = edit_nic.iface
|
||||
modes = ['DHCP (auto detect)', 'IP (static)']
|
||||
default_mode = 'DHCP (auto detect)'
|
||||
modes = ["DHCP (auto detect)", "IP (static)"]
|
||||
default_mode = "DHCP (auto detect)"
|
||||
|
||||
header = str(_('Select which mode to configure for "{}"').format(iface_name)) + '\n'
|
||||
header = str(_('Select which mode to configure for "{}"').format(iface_name)) + "\n"
|
||||
items = [MenuItem(m, value=m) for m in modes]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
group.set_default_by_value(default_mode)
|
||||
|
|
@ -137,43 +137,43 @@ class ManualNetworkConfig(ListManager[Nic]):
|
|||
header=header,
|
||||
allow_skip=False,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Modes')))
|
||||
frame=FrameProperties.min(str(_("Modes"))),
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
mode = result.get_value()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
case ResultType.Skip:
|
||||
raise ValueError('The mode menu should not be skippable')
|
||||
raise ValueError("The mode menu should not be skippable")
|
||||
case _:
|
||||
assert_never(result.type_)
|
||||
|
||||
if mode == 'IP (static)':
|
||||
header = str(_('Enter the IP and subnet for {} (example: 192.168.0.5/24): ').format(iface_name)) + '\n'
|
||||
ip = self._get_ip_address(str(_('IP address')), header, False, False)
|
||||
if mode == "IP (static)":
|
||||
header = str(_("Enter the IP and subnet for {} (example: 192.168.0.5/24): ").format(iface_name)) + "\n"
|
||||
ip = self._get_ip_address(str(_("IP address")), header, False, False)
|
||||
|
||||
header = str(_('Enter your gateway (router) IP address (leave blank for none)')) + '\n'
|
||||
gateway = self._get_ip_address(str(_('Gateway address')), header, True, False)
|
||||
header = str(_("Enter your gateway (router) IP address (leave blank for none)")) + "\n"
|
||||
gateway = self._get_ip_address(str(_("Gateway address")), header, True, False)
|
||||
|
||||
if edit_nic.dns:
|
||||
display_dns = ' '.join(edit_nic.dns)
|
||||
display_dns = " ".join(edit_nic.dns)
|
||||
else:
|
||||
display_dns = None
|
||||
|
||||
header = str(_('Enter your DNS servers with space separated (leave blank for none)')) + '\n'
|
||||
header = str(_("Enter your DNS servers with space separated (leave blank for none)")) + "\n"
|
||||
dns_servers = self._get_ip_address(
|
||||
str(_('DNS servers')),
|
||||
str(_("DNS servers")),
|
||||
header,
|
||||
True,
|
||||
True,
|
||||
display_dns
|
||||
display_dns,
|
||||
)
|
||||
|
||||
dns = []
|
||||
if dns_servers is not None:
|
||||
dns = dns_servers.split(' ')
|
||||
dns = dns_servers.split(" ")
|
||||
|
||||
return Nic(iface=iface_name, ip=ip, gateway=gateway, dns=dns, dhcp=False)
|
||||
else:
|
||||
|
|
@ -195,9 +195,9 @@ def ask_to_configure_network(preset: NetworkConfiguration | None) -> NetworkConf
|
|||
result = SelectMenu[NetworkConfiguration](
|
||||
group,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Network configuration'))),
|
||||
frame=FrameProperties.min(str(_("Network configuration"))),
|
||||
allow_reset=True,
|
||||
allow_skip=True
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -215,7 +215,7 @@ def ask_to_configure_network(preset: NetworkConfiguration | None) -> NetworkConf
|
|||
return NetworkConfiguration(NicType.NM)
|
||||
case NicType.MANUAL:
|
||||
preset_nics = preset.nics if preset else []
|
||||
nics = ManualNetworkConfig(str(_('Configure interfaces')), preset_nics).run()
|
||||
nics = ManualNetworkConfig(str(_("Configure interfaces")), preset_nics).run()
|
||||
|
||||
if nics:
|
||||
return NetworkConfiguration(NicType.MANUAL, nics)
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ def select_kernel(preset: list[str] = []) -> list[str]:
|
|||
allow_skip=True,
|
||||
allow_reset=True,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Kernel'))),
|
||||
multi=True
|
||||
frame=FrameProperties.min(str(_("Kernel"))),
|
||||
multi=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -58,7 +58,7 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
|
|||
if not SysInfo.has_uefi():
|
||||
options = [Bootloader.Grub, Bootloader.Limine]
|
||||
default = Bootloader.Grub
|
||||
header = str(_('UEFI is not detected and some options are disabled'))
|
||||
header = str(_("UEFI is not detected and some options are disabled"))
|
||||
else:
|
||||
options = [b for b in Bootloader]
|
||||
default = Bootloader.Systemd
|
||||
|
|
@ -73,8 +73,8 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
|
|||
group,
|
||||
header=header,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Bootloader'))),
|
||||
allow_skip=True
|
||||
frame=FrameProperties.min(str(_("Bootloader"))),
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -83,11 +83,11 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
|
|||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
|
||||
def ask_for_uki(preset: bool = True) -> bool:
|
||||
prompt = str(_('Would you like to use unified kernel images?')) + '\n'
|
||||
prompt = str(_("Would you like to use unified kernel images?")) + "\n"
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_focus_by_value(preset)
|
||||
|
|
@ -98,7 +98,7 @@ def ask_for_uki(preset: bool = True) -> bool:
|
|||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -107,7 +107,7 @@ def ask_for_uki(preset: bool = True) -> bool:
|
|||
case ResultType.Selection:
|
||||
return result.item() == MenuItem.yes()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
|
||||
def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None) -> GfxDriver | None:
|
||||
|
|
@ -128,22 +128,22 @@ def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None
|
|||
if preset is not None:
|
||||
group.set_focus_by_value(preset)
|
||||
|
||||
header = ''
|
||||
header = ""
|
||||
if SysInfo.has_amd_graphics():
|
||||
header += str(_('For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options.')) + '\n'
|
||||
header += str(_("For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options.")) + "\n"
|
||||
if SysInfo.has_intel_graphics():
|
||||
header += str(_('For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n'))
|
||||
header += str(_("For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n"))
|
||||
if SysInfo.has_nvidia_graphics():
|
||||
header += str(_('For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n'))
|
||||
header += str(_("For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n"))
|
||||
|
||||
result = SelectMenu[GfxDriver](
|
||||
group,
|
||||
header=header,
|
||||
allow_skip=True,
|
||||
allow_reset=True,
|
||||
preview_size='auto',
|
||||
preview_size="auto",
|
||||
preview_style=PreviewStyle.BOTTOM,
|
||||
preview_frame=FrameProperties(str(_('Info')), h_frame_style=FrameStyle.MIN)
|
||||
preview_frame=FrameProperties(str(_("Info")), h_frame_style=FrameStyle.MIN),
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -161,7 +161,7 @@ def ask_for_swap(preset: bool = True) -> bool:
|
|||
else:
|
||||
default_item = MenuItem.no()
|
||||
|
||||
prompt = str(_('Would you like to use swap on zram?')) + '\n'
|
||||
prompt = str(_("Would you like to use swap on zram?")) + "\n"
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_focus_by_value(default_item)
|
||||
|
|
@ -172,7 +172,7 @@ def ask_for_swap(preset: bool = True) -> bool:
|
|||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -181,6 +181,6 @@ def ask_for_swap(preset: bool = True) -> bool:
|
|||
case ResultType.Selection:
|
||||
return result.item() == MenuItem.yes()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
return preset
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ from .utils import (
|
|||
)
|
||||
|
||||
__all__ = [
|
||||
'list_keyboard_languages',
|
||||
'list_locales',
|
||||
'list_timezones',
|
||||
'list_x11_keyboard_languages',
|
||||
'set_kb_layout',
|
||||
'verify_keyboard_layout',
|
||||
'verify_x11_keyboard_layout',
|
||||
"list_keyboard_languages",
|
||||
"list_locales",
|
||||
"list_timezones",
|
||||
"list_x11_keyboard_languages",
|
||||
"set_kb_layout",
|
||||
"verify_keyboard_layout",
|
||||
"verify_x11_keyboard_layout",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ if TYPE_CHECKING:
|
|||
class LocaleMenu(AbstractSubMenu[LocaleConfiguration]):
|
||||
def __init__(
|
||||
self,
|
||||
locale_conf: LocaleConfiguration
|
||||
locale_conf: LocaleConfiguration,
|
||||
):
|
||||
self._locale_conf = locale_conf
|
||||
menu_optioons = self._define_menu_options()
|
||||
|
|
@ -29,39 +29,39 @@ class LocaleMenu(AbstractSubMenu[LocaleConfiguration]):
|
|||
super().__init__(
|
||||
self._item_group,
|
||||
config=self._locale_conf,
|
||||
allow_reset=True
|
||||
allow_reset=True,
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=str(_('Keyboard layout')),
|
||||
text=str(_("Keyboard layout")),
|
||||
action=self._select_kb_layout,
|
||||
value=self._locale_conf.kb_layout,
|
||||
preview_action=self._prev_locale,
|
||||
key='kb_layout'
|
||||
key="kb_layout",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Locale language')),
|
||||
text=str(_("Locale language")),
|
||||
action=select_locale_lang,
|
||||
value=self._locale_conf.sys_lang,
|
||||
preview_action=self._prev_locale,
|
||||
key='sys_lang'
|
||||
key="sys_lang",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Locale encoding')),
|
||||
text=str(_("Locale encoding")),
|
||||
action=select_locale_enc,
|
||||
value=self._locale_conf.sys_enc,
|
||||
preview_action=self._prev_locale,
|
||||
key='sys_enc'
|
||||
)
|
||||
key="sys_enc",
|
||||
),
|
||||
]
|
||||
|
||||
def _prev_locale(self, item: MenuItem) -> str | None:
|
||||
temp_locale = LocaleConfiguration(
|
||||
self._menu_item_group.find_by_key('kb_layout').get_value(),
|
||||
self._menu_item_group.find_by_key('sys_lang').get_value(),
|
||||
self._menu_item_group.find_by_key('sys_enc').get_value(),
|
||||
self._menu_item_group.find_by_key("kb_layout").get_value(),
|
||||
self._menu_item_group.find_by_key("sys_lang").get_value(),
|
||||
self._menu_item_group.find_by_key("sys_enc").get_value(),
|
||||
)
|
||||
return temp_locale.preview()
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ def select_locale_lang(preset: str | None = None) -> str | None:
|
|||
result = SelectMenu[str](
|
||||
group,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Locale language'))),
|
||||
frame=FrameProperties.min(str(_("Locale language"))),
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ def select_locale_lang(preset: str | None = None) -> str | None:
|
|||
case ResultType.Skip:
|
||||
return preset
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
|
||||
def select_locale_enc(preset: str | None = None) -> str | None:
|
||||
|
|
@ -112,7 +112,7 @@ def select_locale_enc(preset: str | None = None) -> str | None:
|
|||
result = SelectMenu[str](
|
||||
group,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Locale encoding'))),
|
||||
frame=FrameProperties.min(str(_("Locale encoding"))),
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ def select_locale_enc(preset: str | None = None) -> str | None:
|
|||
case ResultType.Skip:
|
||||
return preset
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
|
||||
def select_kb_layout(preset: str | None = None) -> str | None:
|
||||
|
|
@ -144,7 +144,7 @@ def select_kb_layout(preset: str | None = None) -> str | None:
|
|||
result = SelectMenu[str](
|
||||
group,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Keyboard layout'))),
|
||||
frame=FrameProperties.min(str(_("Keyboard layout"))),
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
|
|
@ -154,6 +154,6 @@ def select_kb_layout(preset: str | None = None) -> str | None:
|
|||
case ResultType.Skip:
|
||||
return preset
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -4,28 +4,36 @@ from ..output import error
|
|||
|
||||
|
||||
def list_keyboard_languages() -> list[str]:
|
||||
return SysCommand(
|
||||
"localectl --no-pager list-keymaps",
|
||||
environment_vars={'SYSTEMD_COLORS': '0'}
|
||||
).decode().splitlines()
|
||||
return (
|
||||
SysCommand(
|
||||
"localectl --no-pager list-keymaps",
|
||||
environment_vars={"SYSTEMD_COLORS": "0"},
|
||||
)
|
||||
.decode()
|
||||
.splitlines()
|
||||
)
|
||||
|
||||
|
||||
def list_locales() -> list[str]:
|
||||
locales = []
|
||||
|
||||
with open('/usr/share/i18n/SUPPORTED') as file:
|
||||
with open("/usr/share/i18n/SUPPORTED") as file:
|
||||
for line in file:
|
||||
if line != 'C.UTF-8 UTF-8\n':
|
||||
if line != "C.UTF-8 UTF-8\n":
|
||||
locales.append(line.rstrip())
|
||||
|
||||
return locales
|
||||
|
||||
|
||||
def list_x11_keyboard_languages() -> list[str]:
|
||||
return SysCommand(
|
||||
"localectl --no-pager list-x11-keymap-layouts",
|
||||
environment_vars={'SYSTEMD_COLORS': '0'}
|
||||
).decode().splitlines()
|
||||
return (
|
||||
SysCommand(
|
||||
"localectl --no-pager list-x11-keymap-layouts",
|
||||
environment_vars={"SYSTEMD_COLORS": "0"},
|
||||
)
|
||||
.decode()
|
||||
.splitlines()
|
||||
)
|
||||
|
||||
|
||||
def verify_keyboard_layout(layout: str) -> bool:
|
||||
|
|
@ -44,10 +52,14 @@ def verify_x11_keyboard_layout(layout: str) -> bool:
|
|||
|
||||
def get_kb_layout() -> str:
|
||||
try:
|
||||
lines = SysCommand(
|
||||
"localectl --no-pager status",
|
||||
environment_vars={'SYSTEMD_COLORS': '0'}
|
||||
).decode().splitlines()
|
||||
lines = (
|
||||
SysCommand(
|
||||
"localectl --no-pager status",
|
||||
environment_vars={"SYSTEMD_COLORS": "0"},
|
||||
)
|
||||
.decode()
|
||||
.splitlines()
|
||||
)
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
|
@ -73,7 +85,7 @@ def set_kb_layout(locale: str) -> bool:
|
|||
return False
|
||||
|
||||
try:
|
||||
SysCommand(f'localectl set-keymap {locale}')
|
||||
SysCommand(f"localectl set-keymap {locale}")
|
||||
except SysCallError as err:
|
||||
raise ServiceException(f"Unable to set locale '{locale}' for console: {err}")
|
||||
|
||||
|
|
@ -83,7 +95,11 @@ def set_kb_layout(locale: str) -> bool:
|
|||
|
||||
|
||||
def list_timezones() -> list[str]:
|
||||
return SysCommand(
|
||||
"timedatectl --no-pager list-timezones",
|
||||
environment_vars={'SYSTEMD_COLORS': '0'}
|
||||
).decode().splitlines()
|
||||
return (
|
||||
SysCommand(
|
||||
"timedatectl --no-pager list-timezones",
|
||||
environment_vars={"SYSTEMD_COLORS": "0"},
|
||||
)
|
||||
.decode()
|
||||
.splitlines()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,25 +24,25 @@ class Luks2:
|
|||
@property
|
||||
def mapper_dev(self) -> Path | None:
|
||||
if self.mapper_name:
|
||||
return Path(f'/dev/mapper/{self.mapper_name}')
|
||||
return Path(f"/dev/mapper/{self.mapper_name}")
|
||||
return None
|
||||
|
||||
def isLuks(self) -> bool:
|
||||
try:
|
||||
SysCommand(f'cryptsetup isLuks {self.luks_dev_path}')
|
||||
SysCommand(f"cryptsetup isLuks {self.luks_dev_path}")
|
||||
return True
|
||||
except SysCallError:
|
||||
return False
|
||||
|
||||
def erase(self) -> None:
|
||||
debug(f'Erasing luks partition: {self.luks_dev_path}')
|
||||
worker = SysCommandWorker(f'cryptsetup erase {self.luks_dev_path}')
|
||||
debug(f"Erasing luks partition: {self.luks_dev_path}")
|
||||
worker = SysCommandWorker(f"cryptsetup erase {self.luks_dev_path}")
|
||||
worker.poll()
|
||||
worker.write(b'YES\n', line_ending=False)
|
||||
worker.write(b"YES\n", line_ending=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.luks_dev_path is None:
|
||||
raise ValueError('Partition must have a path set')
|
||||
raise ValueError("Partition must have a path set")
|
||||
|
||||
def __enter__(self) -> None:
|
||||
self.unlock(self.key_file)
|
||||
|
|
@ -53,50 +53,56 @@ class Luks2:
|
|||
|
||||
def _password_bytes(self) -> bytes:
|
||||
if not self.password:
|
||||
raise ValueError('Password for luks2 device was not specified')
|
||||
raise ValueError("Password for luks2 device was not specified")
|
||||
|
||||
if isinstance(self.password, bytes):
|
||||
return self.password
|
||||
else:
|
||||
return bytes(self.password.plaintext, 'UTF-8')
|
||||
return bytes(self.password.plaintext, "UTF-8")
|
||||
|
||||
def _get_passphrase_args(
|
||||
self,
|
||||
key_file: Path | None = None
|
||||
key_file: Path | None = None,
|
||||
) -> tuple[list[str], bytes | None]:
|
||||
key_file = key_file or self.key_file
|
||||
|
||||
if key_file:
|
||||
return ['--key-file', str(key_file)], None
|
||||
return ["--key-file", str(key_file)], None
|
||||
|
||||
return [], self._password_bytes()
|
||||
|
||||
def encrypt(
|
||||
self,
|
||||
key_size: int = 512,
|
||||
hash_type: str = 'sha512',
|
||||
hash_type: str = "sha512",
|
||||
iter_time: int = 10000,
|
||||
key_file: Path | None = None
|
||||
key_file: Path | None = None,
|
||||
) -> Path | None:
|
||||
debug(f'Luks2 encrypting: {self.luks_dev_path}')
|
||||
debug(f"Luks2 encrypting: {self.luks_dev_path}")
|
||||
|
||||
key_file_arg, passphrase = self._get_passphrase_args(key_file)
|
||||
|
||||
cmd = [
|
||||
'cryptsetup',
|
||||
'--batch-mode',
|
||||
'--verbose',
|
||||
'--type', 'luks2',
|
||||
'--pbkdf', 'argon2id',
|
||||
'--hash', hash_type,
|
||||
'--key-size', str(key_size),
|
||||
'--iter-time', str(iter_time),
|
||||
"cryptsetup",
|
||||
"--batch-mode",
|
||||
"--verbose",
|
||||
"--type",
|
||||
"luks2",
|
||||
"--pbkdf",
|
||||
"argon2id",
|
||||
"--hash",
|
||||
hash_type,
|
||||
"--key-size",
|
||||
str(key_size),
|
||||
"--iter-time",
|
||||
str(iter_time),
|
||||
*key_file_arg,
|
||||
'--use-urandom',
|
||||
'luksFormat', str(self.luks_dev_path)
|
||||
"--use-urandom",
|
||||
"luksFormat",
|
||||
str(self.luks_dev_path),
|
||||
]
|
||||
|
||||
debug(f'cryptsetup format: {shlex.join(cmd)}')
|
||||
debug(f"cryptsetup format: {shlex.join(cmd)}")
|
||||
|
||||
try:
|
||||
result = run(cmd, input_data=passphrase)
|
||||
|
|
@ -104,23 +110,23 @@ class Luks2:
|
|||
output = err.stdout.decode().rstrip()
|
||||
raise DiskError(f'Could not encrypt volume "{self.luks_dev_path}": {output}')
|
||||
|
||||
debug(f'cryptsetup luksFormat output: {result.stdout.decode().rstrip()}')
|
||||
debug(f"cryptsetup luksFormat output: {result.stdout.decode().rstrip()}")
|
||||
|
||||
self.key_file = key_file
|
||||
|
||||
return key_file
|
||||
|
||||
def _get_luks_uuid(self) -> str:
|
||||
command = f'cryptsetup luksUUID {self.luks_dev_path}'
|
||||
command = f"cryptsetup luksUUID {self.luks_dev_path}"
|
||||
|
||||
try:
|
||||
return SysCommand(command).decode()
|
||||
except SysCallError as err:
|
||||
info(f'Unable to get UUID for Luks device: {self.luks_dev_path}')
|
||||
info(f"Unable to get UUID for Luks device: {self.luks_dev_path}")
|
||||
raise err
|
||||
|
||||
def is_unlocked(self) -> bool:
|
||||
return self.mapper_name is not None and Path(f'/dev/mapper/{self.mapper_name}').exists()
|
||||
return self.mapper_name is not None and Path(f"/dev/mapper/{self.mapper_name}").exists()
|
||||
|
||||
def unlock(self, key_file: Path | None = None) -> None:
|
||||
"""
|
||||
|
|
@ -130,27 +136,29 @@ class Luks2:
|
|||
:param key_file: An alternative key file
|
||||
:type key_file: Path
|
||||
"""
|
||||
debug(f'Unlocking luks2 device: {self.luks_dev_path}')
|
||||
debug(f"Unlocking luks2 device: {self.luks_dev_path}")
|
||||
|
||||
if not self.mapper_name:
|
||||
raise ValueError('mapper name missing')
|
||||
raise ValueError("mapper name missing")
|
||||
|
||||
key_file_arg, passphrase = self._get_passphrase_args(key_file)
|
||||
|
||||
cmd = [
|
||||
'cryptsetup', 'open',
|
||||
"cryptsetup",
|
||||
"open",
|
||||
str(self.luks_dev_path),
|
||||
str(self.mapper_name),
|
||||
*key_file_arg,
|
||||
'--type', 'luks2'
|
||||
"--type",
|
||||
"luks2",
|
||||
]
|
||||
|
||||
result = run(cmd, input_data=passphrase)
|
||||
|
||||
debug(f'cryptsetup open output: {result.stdout.decode().rstrip()}')
|
||||
debug(f"cryptsetup open output: {result.stdout.decode().rstrip()}")
|
||||
|
||||
if not self.mapper_dev or not self.mapper_dev.is_symlink():
|
||||
raise DiskError(f'Failed to open luks2 device: {self.luks_dev_path}')
|
||||
raise DiskError(f"Failed to open luks2 device: {self.luks_dev_path}")
|
||||
|
||||
def lock(self) -> None:
|
||||
umount(self.luks_dev_path)
|
||||
|
|
@ -163,7 +171,7 @@ class Luks2:
|
|||
for child in lsblk_info.children:
|
||||
# Unmount the child location
|
||||
for mountpoint in child.mountpoints:
|
||||
debug(f'Unmounting {mountpoint}')
|
||||
debug(f"Unmounting {mountpoint}")
|
||||
umount(mountpoint, recursive=True)
|
||||
|
||||
# And close it if possible.
|
||||
|
|
@ -175,20 +183,20 @@ class Luks2:
|
|||
Routine to create keyfiles, so it can be moved elsewhere
|
||||
"""
|
||||
if self.mapper_name is None:
|
||||
raise ValueError('Mapper name must be provided')
|
||||
raise ValueError("Mapper name must be provided")
|
||||
|
||||
# Once we store the key as ../xyzloop.key systemd-cryptsetup can
|
||||
# automatically load this key if we name the device to "xyzloop"
|
||||
kf_path = Path(f'/etc/cryptsetup-keys.d/{self.mapper_name}.key')
|
||||
kf_path = Path(f"/etc/cryptsetup-keys.d/{self.mapper_name}.key")
|
||||
key_file = target_path / kf_path.relative_to(kf_path.root)
|
||||
crypttab_path = target_path / 'etc/crypttab'
|
||||
crypttab_path = target_path / "etc/crypttab"
|
||||
|
||||
if key_file.exists():
|
||||
if not override:
|
||||
info(f'Key file {key_file} already exists, keeping existing')
|
||||
info(f"Key file {key_file} already exists, keeping existing")
|
||||
return
|
||||
else:
|
||||
info(f'Key file {key_file} already exists, overriding')
|
||||
info(f"Key file {key_file} already exists, overriding")
|
||||
|
||||
key_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
|
@ -201,30 +209,30 @@ class Luks2:
|
|||
self._crypttab(crypttab_path, kf_path, options=["luks", "key-slot=1"])
|
||||
|
||||
def _add_key(self, key_file: Path) -> None:
|
||||
debug(f'Adding additional key-file {key_file}')
|
||||
debug(f"Adding additional key-file {key_file}")
|
||||
|
||||
command = f'cryptsetup -q -v luksAddKey {self.luks_dev_path} {key_file}'
|
||||
command = f"cryptsetup -q -v luksAddKey {self.luks_dev_path} {key_file}"
|
||||
worker = SysCommandWorker(command)
|
||||
pw_injected = False
|
||||
|
||||
while worker.is_alive():
|
||||
if b'Enter any existing passphrase' in worker and pw_injected is False:
|
||||
if b"Enter any existing passphrase" in worker and pw_injected is False:
|
||||
worker.write(self._password_bytes())
|
||||
pw_injected = True
|
||||
|
||||
if worker.exit_code != 0:
|
||||
raise DiskError(f'Could not add encryption key {key_file} to {self.luks_dev_path}: {worker.decode()}')
|
||||
raise DiskError(f"Could not add encryption key {key_file} to {self.luks_dev_path}: {worker.decode()}")
|
||||
|
||||
def _crypttab(
|
||||
self,
|
||||
crypttab_path: Path,
|
||||
key_file: Path,
|
||||
options: list[str]
|
||||
options: list[str],
|
||||
) -> None:
|
||||
debug(f'Adding crypttab entry for key {key_file}')
|
||||
debug(f"Adding crypttab entry for key {key_file}")
|
||||
|
||||
with open(crypttab_path, 'a') as crypttab:
|
||||
opt = ','.join(options)
|
||||
with open(crypttab_path, "a") as crypttab:
|
||||
opt = ",".join(options)
|
||||
uuid = self._get_luks_uuid()
|
||||
row = f"{self.mapper_name} UUID={uuid} {key_file} {opt}\n"
|
||||
crypttab.write(row)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from .abstract_menu import AbstractMenu, AbstractSubMenu
|
|||
from .list_manager import ListManager
|
||||
|
||||
__all__ = [
|
||||
'AbstractMenu',
|
||||
'AbstractSubMenu',
|
||||
'ListManager',
|
||||
"AbstractMenu",
|
||||
"AbstractSubMenu",
|
||||
"ListManager",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ if TYPE_CHECKING:
|
|||
_: Callable[[str], DeferredTranslation]
|
||||
|
||||
|
||||
CONFIG_KEY = '__config__'
|
||||
CONFIG_KEY = "__config__"
|
||||
|
||||
|
||||
class AbstractMenu[ValueT]:
|
||||
|
|
@ -26,7 +26,7 @@ class AbstractMenu[ValueT]:
|
|||
config: Any,
|
||||
auto_cursor: bool = True,
|
||||
allow_reset: bool = False,
|
||||
reset_warning: str | None = None
|
||||
reset_warning: str | None = None,
|
||||
):
|
||||
self._menu_item_group = item_group
|
||||
self._config = config
|
||||
|
|
@ -88,7 +88,7 @@ class AbstractMenu[ValueT]:
|
|||
found = True
|
||||
|
||||
if not found:
|
||||
raise ValueError(f'No selector found: {key}')
|
||||
raise ValueError(f"No selector found: {key}")
|
||||
|
||||
def disable_all(self) -> None:
|
||||
for item in self._menu_item_group.items:
|
||||
|
|
@ -107,8 +107,8 @@ class AbstractMenu[ValueT]:
|
|||
allow_reset=self._allow_reset,
|
||||
reset_warning_msg=self._reset_warning,
|
||||
preview_style=PreviewStyle.RIGHT,
|
||||
preview_size='auto',
|
||||
preview_frame=FrameProperties('Info', FrameStyle.MAX),
|
||||
preview_size="auto",
|
||||
preview_frame=FrameProperties("Info", FrameStyle.MAX),
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -132,14 +132,14 @@ class AbstractSubMenu[ValueT](AbstractMenu[ValueT]):
|
|||
item_group: MenuItemGroup,
|
||||
config: Any,
|
||||
auto_cursor: bool = True,
|
||||
allow_reset: bool = False
|
||||
allow_reset: bool = False,
|
||||
):
|
||||
back_text = f'{Chars.Right_arrow} ' + str(_('Back'))
|
||||
back_text = f"{Chars.Right_arrow} " + str(_("Back"))
|
||||
item_group.add_item(MenuItem(text=back_text))
|
||||
|
||||
super().__init__(
|
||||
item_group,
|
||||
config=config,
|
||||
auto_cursor=auto_cursor,
|
||||
allow_reset=allow_reset
|
||||
allow_reset=allow_reset,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class ListManager[ValueT]:
|
|||
entries: list[ValueT],
|
||||
base_actions: list[str],
|
||||
sub_menu_actions: list[str],
|
||||
prompt: str | None = None
|
||||
prompt: str | None = None,
|
||||
):
|
||||
"""
|
||||
:param prompt: Text which will appear at the header
|
||||
|
|
@ -42,9 +42,9 @@ class ListManager[ValueT]:
|
|||
|
||||
self._prompt = prompt
|
||||
|
||||
self._separator = ''
|
||||
self._confirm_action = str(_('Confirm and exit'))
|
||||
self._cancel_action = str(_('Cancel'))
|
||||
self._separator = ""
|
||||
self._confirm_action = str(_("Confirm and exit"))
|
||||
self._cancel_action = str(_("Cancel"))
|
||||
|
||||
self._terminate_actions = [self._confirm_action, self._cancel_action]
|
||||
self._base_actions = base_actions
|
||||
|
|
@ -67,12 +67,12 @@ class ListManager[ValueT]:
|
|||
while True:
|
||||
group = MenuHelper(
|
||||
data=self._data,
|
||||
additional_options=additional_options
|
||||
additional_options=additional_options,
|
||||
).create_menu_group()
|
||||
|
||||
prompt = None
|
||||
if self._prompt is not None:
|
||||
prompt = f'{self._prompt}\n\n'
|
||||
prompt = f"{self._prompt}\n\n"
|
||||
|
||||
prompt = None
|
||||
|
||||
|
|
@ -81,14 +81,14 @@ class ListManager[ValueT]:
|
|||
header=prompt,
|
||||
search_enabled=False,
|
||||
allow_skip=False,
|
||||
alignment=Alignment.CENTER
|
||||
alignment=Alignment.CENTER,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
value = result.get_value()
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
if value in self._base_actions:
|
||||
value = cast(str, value)
|
||||
|
|
@ -114,21 +114,21 @@ class ListManager[ValueT]:
|
|||
items = [MenuItem(o, value=o) for o in options]
|
||||
group = MenuItemGroup(items, sort_items=False)
|
||||
|
||||
header = f'{self.selected_action_display(entry)}\n'
|
||||
header = f"{self.selected_action_display(entry)}\n"
|
||||
|
||||
result = SelectMenu[str](
|
||||
group,
|
||||
header=header,
|
||||
search_enabled=False,
|
||||
allow_skip=False,
|
||||
alignment=Alignment.CENTER
|
||||
alignment=Alignment.CENTER,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
value = result.get_value()
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
if value != self._cancel_action:
|
||||
self._data = self.handle_action(value, entry, self._data)
|
||||
|
|
@ -138,14 +138,14 @@ class ListManager[ValueT]:
|
|||
this will return the value to be displayed in the
|
||||
"Select an action for '{}'" string
|
||||
"""
|
||||
raise NotImplementedError('Please implement me in the child class')
|
||||
raise NotImplementedError("Please implement me in the child class")
|
||||
|
||||
def handle_action(self, action: str, entry: ValueT | None, data: list[ValueT]) -> list[ValueT]:
|
||||
"""
|
||||
this function is called when a base action or
|
||||
a specific action for an entry is triggered
|
||||
"""
|
||||
raise NotImplementedError('Please implement me in the child class')
|
||||
raise NotImplementedError("Please implement me in the child class")
|
||||
|
||||
def filter_options(self, selection: ValueT, options: list[str]) -> list[str]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ class MenuHelper:
|
|||
def __init__(
|
||||
self,
|
||||
data: list[Any],
|
||||
additional_options: list[str] = []
|
||||
additional_options: list[str] = [],
|
||||
) -> None:
|
||||
self._separator = ''
|
||||
self._separator = ""
|
||||
self._data = data
|
||||
self._additional_options = additional_options
|
||||
|
||||
|
|
@ -39,10 +39,10 @@ class MenuHelper:
|
|||
|
||||
if data:
|
||||
table = FormattedOutput.as_table(data)
|
||||
rows = table.split('\n')
|
||||
rows = table.split("\n")
|
||||
|
||||
# these are the header rows of the table
|
||||
display_data = {f'{rows[0]}': None, f'{rows[1]}': None}
|
||||
display_data = {f"{rows[0]}": None, f"{rows[1]}": None}
|
||||
|
||||
for row, entry in zip(rows[2:], data):
|
||||
display_data[row] = entry
|
||||
|
|
|
|||
|
|
@ -35,16 +35,16 @@ if TYPE_CHECKING:
|
|||
class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
||||
def __init__(self, custom_repositories: list[CustomRepository]):
|
||||
self._actions = [
|
||||
str(_('Add a custom repository')),
|
||||
str(_('Change custom repository')),
|
||||
str(_('Delete custom repository'))
|
||||
str(_("Add a custom repository")),
|
||||
str(_("Change custom repository")),
|
||||
str(_("Delete custom repository")),
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
custom_repositories,
|
||||
[self._actions[0]],
|
||||
self._actions[1:],
|
||||
''
|
||||
"",
|
||||
)
|
||||
|
||||
@override
|
||||
|
|
@ -56,7 +56,7 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
|||
self,
|
||||
action: str,
|
||||
entry: CustomRepository | None,
|
||||
data: list[CustomRepository]
|
||||
data: list[CustomRepository],
|
||||
) -> list[CustomRepository]:
|
||||
if action == self._actions[0]: # add
|
||||
new_repo = self._add_custom_repository()
|
||||
|
|
@ -75,10 +75,10 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
|||
|
||||
def _add_custom_repository(self, preset: CustomRepository | None = None) -> CustomRepository | None:
|
||||
edit_result = EditMenu(
|
||||
str(_('Repository name')),
|
||||
str(_("Repository name")),
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True,
|
||||
default_text=preset.name if preset else None
|
||||
default_text=preset.name if preset else None,
|
||||
).input()
|
||||
|
||||
match edit_result.type_:
|
||||
|
|
@ -87,16 +87,16 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
|||
case ResultType.Skip:
|
||||
return preset
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
header = f'{_("Name")}: {name}'
|
||||
header = f"{_('Name')}: {name}"
|
||||
|
||||
edit_result = EditMenu(
|
||||
str(_('Url')),
|
||||
str(_("Url")),
|
||||
header=header,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True,
|
||||
default_text=preset.url if preset else None
|
||||
default_text=preset.url if preset else None,
|
||||
).input()
|
||||
|
||||
match edit_result.type_:
|
||||
|
|
@ -105,10 +105,10 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
|||
case ResultType.Skip:
|
||||
return preset
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
header += f'\n{_("Url")}: {url}\n'
|
||||
prompt = f'{header}\n' + str(_('Select signature check'))
|
||||
header += f"\n{_('Url')}: {url}\n"
|
||||
prompt = f"{header}\n" + str(_("Select signature check"))
|
||||
|
||||
sign_chk_items = [MenuItem(s.value, value=s.value) for s in SignCheck]
|
||||
group = MenuItemGroup(sign_chk_items, sort_items=False)
|
||||
|
|
@ -120,17 +120,17 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
|||
group,
|
||||
header=prompt,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=False
|
||||
allow_skip=False,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
sign_check = SignCheck(result.get_value())
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
header += f'{_("Signature check")}: {sign_check.value}\n'
|
||||
prompt = f'{header}\n' + 'Select signature option'
|
||||
header += f"{_('Signature check')}: {sign_check.value}\n"
|
||||
prompt = f"{header}\n" + "Select signature option"
|
||||
|
||||
sign_opt_items = [MenuItem(s.value, value=s.value) for s in SignOption]
|
||||
group = MenuItemGroup(sign_opt_items, sort_items=False)
|
||||
|
|
@ -142,14 +142,14 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
|||
group,
|
||||
header=prompt,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=False
|
||||
allow_skip=False,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
sign_opt = SignOption(result.get_value())
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
raise ValueError("Unhandled return type")
|
||||
|
||||
return CustomRepository(name, url, sign_check, sign_opt)
|
||||
|
||||
|
|
@ -157,16 +157,16 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
|||
class CustomMirrorServersList(ListManager[CustomServer]):
|
||||
def __init__(self, custom_servers: list[CustomServer]):
|
||||
self._actions = [
|
||||
str(_('Add a custom server')),
|
||||
str(_('Change custom server')),
|
||||
str(_('Delete custom server'))
|
||||
str(_("Add a custom server")),
|
||||
str(_("Change custom server")),
|
||||
str(_("Delete custom server")),
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
custom_servers,
|
||||
[self._actions[0]],
|
||||
self._actions[1:],
|
||||
''
|
||||
"",
|
||||
)
|
||||
|
||||
@override
|
||||
|
|
@ -178,7 +178,7 @@ class CustomMirrorServersList(ListManager[CustomServer]):
|
|||
self,
|
||||
action: str,
|
||||
entry: CustomServer | None,
|
||||
data: list[CustomServer]
|
||||
data: list[CustomServer],
|
||||
) -> list[CustomServer]:
|
||||
if action == self._actions[0]: # add
|
||||
new_server = self._add_custom_server()
|
||||
|
|
@ -197,10 +197,10 @@ class CustomMirrorServersList(ListManager[CustomServer]):
|
|||
|
||||
def _add_custom_server(self, preset: CustomServer | None = None) -> CustomServer | None:
|
||||
edit_result = EditMenu(
|
||||
str(_('Server url')),
|
||||
str(_("Server url")),
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True,
|
||||
default_text=preset.url if preset else None
|
||||
default_text=preset.url if preset else None,
|
||||
).input()
|
||||
|
||||
match edit_result.type_:
|
||||
|
|
@ -216,7 +216,7 @@ class CustomMirrorServersList(ListManager[CustomServer]):
|
|||
class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
|
||||
def __init__(
|
||||
self,
|
||||
preset: MirrorConfiguration | None = None
|
||||
preset: MirrorConfiguration | None = None,
|
||||
):
|
||||
if preset:
|
||||
self._mirror_config = preset
|
||||
|
|
@ -229,60 +229,60 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
|
|||
super().__init__(
|
||||
self._item_group,
|
||||
config=self._mirror_config,
|
||||
allow_reset=True
|
||||
allow_reset=True,
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=str(_('Select regions')),
|
||||
text=str(_("Select regions")),
|
||||
action=select_mirror_regions,
|
||||
value=self._mirror_config.mirror_regions,
|
||||
preview_action=self._prev_regions,
|
||||
key='mirror_regions'
|
||||
key="mirror_regions",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Add custom servers')),
|
||||
text=str(_("Add custom servers")),
|
||||
action=add_custom_mirror_servers,
|
||||
value=self._mirror_config.custom_servers,
|
||||
preview_action=self._prev_custom_servers,
|
||||
key='custom_servers'
|
||||
key="custom_servers",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Optional repositories')),
|
||||
text=str(_("Optional repositories")),
|
||||
action=select_optional_repositories,
|
||||
value=[],
|
||||
preview_action=self._prev_additional_repos,
|
||||
key='optional_repositories'
|
||||
key="optional_repositories",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Add custom repository')),
|
||||
text=str(_("Add custom repository")),
|
||||
action=select_custom_mirror,
|
||||
value=self._mirror_config.custom_repositories,
|
||||
preview_action=self._prev_custom_mirror,
|
||||
key='custom_repositories'
|
||||
)
|
||||
key="custom_repositories",
|
||||
),
|
||||
]
|
||||
|
||||
def _prev_regions(self, item: MenuItem) -> str | None:
|
||||
regions = item.get_value()
|
||||
|
||||
output = ''
|
||||
output = ""
|
||||
for region in regions:
|
||||
output += f'{region.name}\n'
|
||||
output += f"{region.name}\n"
|
||||
|
||||
for url in region.urls:
|
||||
output += f' - {url}\n'
|
||||
output += f" - {url}\n"
|
||||
|
||||
output += '\n'
|
||||
output += "\n"
|
||||
|
||||
return output
|
||||
|
||||
def _prev_additional_repos(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
repositories: list[Repository] = item.value
|
||||
repos = ', '.join([repo.value for repo in repositories])
|
||||
return f'{_("Additional repositories")}: {repos}'
|
||||
repos = ", ".join([repo.value for repo in repositories])
|
||||
return f"{_('Additional repositories')}: {repos}"
|
||||
return None
|
||||
|
||||
def _prev_custom_mirror(self, item: MenuItem) -> str | None:
|
||||
|
|
@ -298,7 +298,7 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
|
|||
return None
|
||||
|
||||
custom_servers: list[CustomServer] = item.value
|
||||
output = '\n'.join([server.url for server in custom_servers])
|
||||
output = "\n".join([server.url for server in custom_servers])
|
||||
return output.strip()
|
||||
|
||||
@override
|
||||
|
|
@ -308,7 +308,7 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
|
|||
|
||||
|
||||
def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]:
|
||||
Tui.print(str(_('Loading mirror regions...')), clear_screen=True)
|
||||
Tui.print(str(_("Loading mirror regions...")), clear_screen=True)
|
||||
|
||||
mirror_list_handler.load_mirrors()
|
||||
available_regions = mirror_list_handler.get_mirror_regions()
|
||||
|
|
@ -326,7 +326,7 @@ def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]:
|
|||
result = SelectMenu[MirrorRegion](
|
||||
group,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Mirror regions'))),
|
||||
frame=FrameProperties.min(str(_("Mirror regions"))),
|
||||
allow_reset=True,
|
||||
allow_skip=True,
|
||||
multi=True,
|
||||
|
|
@ -368,10 +368,10 @@ def select_optional_repositories(preset: list[Repository]) -> list[Repository]:
|
|||
result = SelectMenu[Repository](
|
||||
group,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min('Additional repositories'),
|
||||
frame=FrameProperties.min("Additional repositories"),
|
||||
allow_reset=True,
|
||||
allow_skip=True,
|
||||
multi=True
|
||||
multi=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -386,7 +386,7 @@ def select_optional_repositories(preset: list[Repository]) -> list[Repository]:
|
|||
class MirrorListHandler:
|
||||
def __init__(
|
||||
self,
|
||||
local_mirrorlist: Path = Path('/etc/pacman.d/mirrorlist'),
|
||||
local_mirrorlist: Path = Path("/etc/pacman.d/mirrorlist"),
|
||||
) -> None:
|
||||
self._local_mirrorlist = local_mirrorlist
|
||||
self._status_mappings: dict[str, list[MirrorStatusEntryV3]] | None = None
|
||||
|
|
@ -411,6 +411,7 @@ class MirrorListHandler:
|
|||
|
||||
def load_mirrors(self) -> None:
|
||||
from .args import arch_config_handler
|
||||
|
||||
if arch_config_handler.args.offline:
|
||||
self.load_local_mirrors()
|
||||
else:
|
||||
|
|
@ -427,14 +428,14 @@ class MirrorListHandler:
|
|||
self._status_mappings = self._parse_remote_mirror_list(mirrorlist)
|
||||
return True
|
||||
except Exception as e:
|
||||
debug(f'Error while fetching mirror list: {e}')
|
||||
debug(f"Error while fetching mirror list: {e}")
|
||||
time.sleep(attempt_nr + 1)
|
||||
|
||||
debug('Unable to fetch mirror list remotely, falling back to local mirror list')
|
||||
debug("Unable to fetch mirror list remotely, falling back to local mirror list")
|
||||
return False
|
||||
|
||||
def load_local_mirrors(self) -> None:
|
||||
with self._local_mirrorlist.open('r') as fp:
|
||||
with self._local_mirrorlist.open("r") as fp:
|
||||
mirrorlist = fp.read()
|
||||
self._status_mappings = self._parse_locale_mirrors(mirrorlist)
|
||||
|
||||
|
|
@ -450,13 +451,15 @@ class MirrorListHandler:
|
|||
|
||||
for mirror in mirror_status.urls:
|
||||
# We filter out mirrors that have bad criteria values
|
||||
if any([
|
||||
mirror.active is False, # Disabled by mirror-list admins
|
||||
mirror.last_sync is None, # Has not synced recently
|
||||
# mirror.score (error rate) over time reported from backend:
|
||||
# https://github.com/archlinux/archweb/blob/31333d3516c91db9a2f2d12260bd61656c011fd1/mirrors/utils.py#L111C22-L111C66
|
||||
(mirror.score is None or mirror.score >= 100),
|
||||
]):
|
||||
if any(
|
||||
[
|
||||
mirror.active is False, # Disabled by mirror-list admins
|
||||
mirror.last_sync is None, # Has not synced recently
|
||||
# mirror.score (error rate) over time reported from backend:
|
||||
# https://github.com/archlinux/archweb/blob/31333d3516c91db9a2f2d12260bd61656c011fd1/mirrors/utils.py#L111C22-L111C66
|
||||
(mirror.score is None or mirror.score >= 100),
|
||||
]
|
||||
):
|
||||
continue
|
||||
|
||||
if mirror.country == "":
|
||||
|
|
@ -466,13 +469,12 @@ class MirrorListHandler:
|
|||
# So we have to assume world-wide
|
||||
mirror.country = "Worldwide"
|
||||
|
||||
if mirror.url.startswith('http'):
|
||||
if mirror.url.startswith("http"):
|
||||
sorting_placeholder.setdefault(mirror.country, []).append(mirror)
|
||||
|
||||
sorted_by_regions: dict[str, list[MirrorStatusEntryV3]] = dict({
|
||||
region: unsorted_mirrors
|
||||
for region, unsorted_mirrors in sorted(sorting_placeholder.items(), key=lambda item: item[0])
|
||||
})
|
||||
sorted_by_regions: dict[str, list[MirrorStatusEntryV3]] = dict(
|
||||
{region: unsorted_mirrors for region, unsorted_mirrors in sorted(sorting_placeholder.items(), key=lambda item: item[0])}
|
||||
)
|
||||
|
||||
return sorted_by_regions
|
||||
|
||||
|
|
@ -484,35 +486,35 @@ class MirrorListHandler:
|
|||
|
||||
mirror_list: dict[str, list[MirrorStatusEntryV3]] = {}
|
||||
|
||||
current_region = ''
|
||||
current_region = ""
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('## '):
|
||||
current_region = line.replace('## ', '').strip()
|
||||
if line.startswith("## "):
|
||||
current_region = line.replace("## ", "").strip()
|
||||
mirror_list.setdefault(current_region, [])
|
||||
|
||||
if line.startswith('Server = '):
|
||||
if line.startswith("Server = "):
|
||||
if not current_region:
|
||||
current_region = 'Local'
|
||||
current_region = "Local"
|
||||
mirror_list.setdefault(current_region, [])
|
||||
|
||||
url = line.removeprefix('Server = ')
|
||||
url = line.removeprefix("Server = ")
|
||||
|
||||
mirror_entry = MirrorStatusEntryV3(
|
||||
url=url.removesuffix('$repo/os/$arch'),
|
||||
url=url.removesuffix("$repo/os/$arch"),
|
||||
protocol=urllib.parse.urlparse(url).scheme,
|
||||
active=True,
|
||||
country=current_region or 'Worldwide',
|
||||
country=current_region or "Worldwide",
|
||||
# The following values are normally populated by
|
||||
# archlinux.org mirror-list endpoint, and can't be known
|
||||
# from just the local mirror-list file.
|
||||
country_code='WW',
|
||||
country_code="WW",
|
||||
isos=True,
|
||||
ipv4=True,
|
||||
ipv6=True,
|
||||
details='Locally defined mirror',
|
||||
details="Locally defined mirror",
|
||||
)
|
||||
|
||||
mirror_list[current_region].append(mirror_entry)
|
||||
|
|
|
|||
|
|
@ -35,46 +35,46 @@ from .profile_model import ProfileConfiguration
|
|||
from .users import PasswordStrength, User
|
||||
|
||||
__all__ = [
|
||||
'Audio',
|
||||
'AudioConfiguration',
|
||||
'BDevice',
|
||||
'Bootloader',
|
||||
'CustomRepository',
|
||||
'DeviceGeometry',
|
||||
'DeviceModification',
|
||||
'DiskEncryption',
|
||||
'DiskLayoutConfiguration',
|
||||
'DiskLayoutType',
|
||||
'EncryptionType',
|
||||
'Fido2Device',
|
||||
'FilesystemType',
|
||||
'LocalPackage',
|
||||
'LocaleConfiguration',
|
||||
'LsblkInfo',
|
||||
'LvmConfiguration',
|
||||
'LvmLayoutType',
|
||||
'LvmVolume',
|
||||
'LvmVolumeGroup',
|
||||
'LvmVolumeStatus',
|
||||
'MirrorConfiguration',
|
||||
'MirrorRegion',
|
||||
'ModificationStatus',
|
||||
'NetworkConfiguration',
|
||||
'Nic',
|
||||
'NicType',
|
||||
'PackageSearch',
|
||||
'PackageSearchResult',
|
||||
'PartitionFlag',
|
||||
'PartitionModification',
|
||||
'PartitionTable',
|
||||
'PartitionType',
|
||||
'PasswordStrength',
|
||||
'ProfileConfiguration',
|
||||
'Repository',
|
||||
'SectorSize',
|
||||
'Size',
|
||||
'SubvolumeModification',
|
||||
'Unit',
|
||||
'User',
|
||||
'_DeviceInfo',
|
||||
"Audio",
|
||||
"AudioConfiguration",
|
||||
"BDevice",
|
||||
"Bootloader",
|
||||
"CustomRepository",
|
||||
"DeviceGeometry",
|
||||
"DeviceModification",
|
||||
"DiskEncryption",
|
||||
"DiskLayoutConfiguration",
|
||||
"DiskLayoutType",
|
||||
"EncryptionType",
|
||||
"Fido2Device",
|
||||
"FilesystemType",
|
||||
"LocalPackage",
|
||||
"LocaleConfiguration",
|
||||
"LsblkInfo",
|
||||
"LvmConfiguration",
|
||||
"LvmLayoutType",
|
||||
"LvmVolume",
|
||||
"LvmVolumeGroup",
|
||||
"LvmVolumeStatus",
|
||||
"MirrorConfiguration",
|
||||
"MirrorRegion",
|
||||
"ModificationStatus",
|
||||
"NetworkConfiguration",
|
||||
"Nic",
|
||||
"NicType",
|
||||
"PackageSearch",
|
||||
"PackageSearchResult",
|
||||
"PartitionFlag",
|
||||
"PartitionModification",
|
||||
"PartitionTable",
|
||||
"PartitionType",
|
||||
"PasswordStrength",
|
||||
"ProfileConfiguration",
|
||||
"Repository",
|
||||
"SectorSize",
|
||||
"Size",
|
||||
"SubvolumeModification",
|
||||
"Unit",
|
||||
"User",
|
||||
"_DeviceInfo",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class Audio(StrEnum):
|
||||
NO_AUDIO = 'No audio server'
|
||||
NO_AUDIO = "No audio server"
|
||||
PIPEWIRE = auto()
|
||||
PULSEAUDIO = auto()
|
||||
|
||||
|
|
@ -21,20 +21,20 @@ class AudioConfiguration:
|
|||
|
||||
def json(self) -> dict[str, str]:
|
||||
return {
|
||||
'audio': self.audio.value
|
||||
"audio": self.audio.value,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def parse_arg(arg: dict[str, str]) -> 'AudioConfiguration':
|
||||
def parse_arg(arg: dict[str, str]) -> "AudioConfiguration":
|
||||
return AudioConfiguration(
|
||||
Audio(arg['audio'])
|
||||
Audio(arg["audio"]),
|
||||
)
|
||||
|
||||
def install_audio_config(
|
||||
self,
|
||||
installation: 'Installer'
|
||||
installation: "Installer",
|
||||
) -> None:
|
||||
info(f'Installing audio server: {self.audio.name}')
|
||||
info(f"Installing audio server: {self.audio.name}")
|
||||
|
||||
from ...default_profiles.applications.pipewire import PipewireProfile
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ class AudioConfiguration:
|
|||
|
||||
if self.audio != Audio.NO_AUDIO:
|
||||
if SysInfo.requires_sof_fw():
|
||||
installation.add_additional_packages('sof-firmware')
|
||||
installation.add_additional_packages("sof-firmware")
|
||||
|
||||
if SysInfo.requires_alsa_fw():
|
||||
installation.add_additional_packages('alsa-firmware')
|
||||
installation.add_additional_packages("alsa-firmware")
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ from ..output import warn
|
|||
|
||||
|
||||
class Bootloader(Enum):
|
||||
Systemd = 'Systemd-boot'
|
||||
Grub = 'Grub'
|
||||
Efistub = 'Efistub'
|
||||
Limine = 'Limine'
|
||||
Systemd = "Systemd-boot"
|
||||
Grub = "Grub"
|
||||
Efistub = "Efistub"
|
||||
Limine = "Limine"
|
||||
|
||||
def has_uki_support(self) -> bool:
|
||||
match self:
|
||||
|
|
@ -40,7 +40,7 @@ class Bootloader(Enum):
|
|||
bootloader = bootloader.capitalize()
|
||||
|
||||
if bootloader not in cls.values():
|
||||
values = ', '.join(cls.values())
|
||||
values = ", ".join(cls.values())
|
||||
warn(f'Invalid bootloader value "{bootloader}". Allowed values: {values}')
|
||||
sys.exit(1)
|
||||
return Bootloader(bootloader)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -18,42 +18,42 @@ class LocaleConfiguration:
|
|||
sys_enc: str
|
||||
|
||||
@staticmethod
|
||||
def default() -> 'LocaleConfiguration':
|
||||
def default() -> "LocaleConfiguration":
|
||||
layout = get_kb_layout()
|
||||
if layout == "":
|
||||
layout = 'us'
|
||||
return LocaleConfiguration(layout, 'en_US.UTF-8', 'UTF-8')
|
||||
layout = "us"
|
||||
return LocaleConfiguration(layout, "en_US.UTF-8", "UTF-8")
|
||||
|
||||
def json(self) -> dict[str, str]:
|
||||
return {
|
||||
'kb_layout': self.kb_layout,
|
||||
'sys_lang': self.sys_lang,
|
||||
'sys_enc': self.sys_enc
|
||||
"kb_layout": self.kb_layout,
|
||||
"sys_lang": self.sys_lang,
|
||||
"sys_enc": self.sys_enc,
|
||||
}
|
||||
|
||||
def preview(self) -> str:
|
||||
output = '{}: {}\n'.format(str(_('Keyboard layout')), self.kb_layout)
|
||||
output += '{}: {}\n'.format(str(_('Locale language')), self.sys_lang)
|
||||
output += '{}: {}'.format(str(_('Locale encoding')), self.sys_enc)
|
||||
output = "{}: {}\n".format(str(_("Keyboard layout")), self.kb_layout)
|
||||
output += "{}: {}\n".format(str(_("Locale language")), self.sys_lang)
|
||||
output += "{}: {}".format(str(_("Locale encoding")), self.sys_enc)
|
||||
return output
|
||||
|
||||
@classmethod
|
||||
def _load_config(cls, config: 'LocaleConfiguration', args: dict[str, str]) -> 'LocaleConfiguration':
|
||||
if 'sys_lang' in args:
|
||||
config.sys_lang = args['sys_lang']
|
||||
if 'sys_enc' in args:
|
||||
config.sys_enc = args['sys_enc']
|
||||
if 'kb_layout' in args:
|
||||
config.kb_layout = args['kb_layout']
|
||||
def _load_config(cls, config: "LocaleConfiguration", args: dict[str, str]) -> "LocaleConfiguration":
|
||||
if "sys_lang" in args:
|
||||
config.sys_lang = args["sys_lang"]
|
||||
if "sys_enc" in args:
|
||||
config.sys_enc = args["sys_enc"]
|
||||
if "kb_layout" in args:
|
||||
config.kb_layout = args["kb_layout"]
|
||||
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def parse_arg(cls, args: dict[str, Any]) -> 'LocaleConfiguration':
|
||||
def parse_arg(cls, args: dict[str, Any]) -> "LocaleConfiguration":
|
||||
default = cls.default()
|
||||
|
||||
if 'locale_config' in args:
|
||||
default = cls._load_config(default, args['locale_config'])
|
||||
if "locale_config" in args:
|
||||
default = cls._load_config(default, args["locale_config"])
|
||||
else:
|
||||
default = cls._load_config(default, args)
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class MirrorStatusEntryV3(BaseModel):
|
|||
|
||||
@property
|
||||
def server_url(self) -> str:
|
||||
return f'{self.url}$repo/os/$arch'
|
||||
return f"{self.url}$repo/os/$arch"
|
||||
|
||||
@property
|
||||
def speed(self) -> float:
|
||||
|
|
@ -101,7 +101,7 @@ class MirrorStatusEntryV3(BaseModel):
|
|||
return self._latency
|
||||
|
||||
@classmethod
|
||||
@field_validator('score', mode='before')
|
||||
@field_validator("score", mode="before")
|
||||
def validate_score(cls, value: float) -> int | None:
|
||||
if value is not None:
|
||||
value = round(value)
|
||||
|
|
@ -109,12 +109,12 @@ class MirrorStatusEntryV3(BaseModel):
|
|||
|
||||
return value
|
||||
|
||||
@model_validator(mode='after')
|
||||
def debug_output(self, validation_info) -> 'MirrorStatusEntryV3':
|
||||
self._hostname, *port = urllib.parse.urlparse(self.url).netloc.split(':', 1)
|
||||
@model_validator(mode="after")
|
||||
def debug_output(self, validation_info) -> "MirrorStatusEntryV3":
|
||||
self._hostname, *port = urllib.parse.urlparse(self.url).netloc.split(":", 1)
|
||||
self._port = int(port[0]) if port and len(port) >= 1 else None
|
||||
|
||||
debug(f"Loaded mirror {self._hostname}" + (f" with current score of {self.score}" if self.score else ''))
|
||||
debug(f"Loaded mirror {self._hostname}" + (f" with current score of {self.score}" if self.score else ""))
|
||||
return self
|
||||
|
||||
|
||||
|
|
@ -125,13 +125,13 @@ class MirrorStatusListV3(BaseModel):
|
|||
urls: list[MirrorStatusEntryV3]
|
||||
version: int
|
||||
|
||||
@model_validator(mode='before')
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def check_model(
|
||||
cls,
|
||||
data: dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]]
|
||||
data: dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]],
|
||||
) -> dict[str, int | datetime.datetime | list[MirrorStatusEntryV3]]:
|
||||
if data.get('version') == 3:
|
||||
if data.get("version") == 3:
|
||||
return data
|
||||
|
||||
raise ValueError("MirrorStatusListV3 only accepts version 3 data from https://archlinux.org/mirrors/status/json/")
|
||||
|
|
@ -153,14 +153,14 @@ class MirrorRegion:
|
|||
|
||||
|
||||
class SignCheck(Enum):
|
||||
Never = 'Never'
|
||||
Optional = 'Optional'
|
||||
Required = 'Required'
|
||||
Never = "Never"
|
||||
Optional = "Optional"
|
||||
Required = "Required"
|
||||
|
||||
|
||||
class SignOption(Enum):
|
||||
TrustedOnly = 'TrustedOnly'
|
||||
TrustAll = 'TrustAll'
|
||||
TrustedOnly = "TrustedOnly"
|
||||
TrustAll = "TrustAll"
|
||||
|
||||
|
||||
class _CustomRepositorySerialization(TypedDict):
|
||||
|
|
@ -179,31 +179,31 @@ class CustomRepository:
|
|||
|
||||
def table_data(self) -> dict[str, str]:
|
||||
return {
|
||||
'Name': self.name,
|
||||
'Url': self.url,
|
||||
'Sign check': self.sign_check.value,
|
||||
'Sign options': self.sign_option.value
|
||||
"Name": self.name,
|
||||
"Url": self.url,
|
||||
"Sign check": self.sign_check.value,
|
||||
"Sign options": self.sign_option.value,
|
||||
}
|
||||
|
||||
def json(self) -> _CustomRepositorySerialization:
|
||||
return {
|
||||
'name': self.name,
|
||||
'url': self.url,
|
||||
'sign_check': self.sign_check.value,
|
||||
'sign_option': self.sign_option.value
|
||||
"name": self.name,
|
||||
"url": self.url,
|
||||
"sign_check": self.sign_check.value,
|
||||
"sign_option": self.sign_option.value,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def parse_args(cls, args: list[dict[str, str]]) -> list['CustomRepository']:
|
||||
def parse_args(cls, args: list[dict[str, str]]) -> list["CustomRepository"]:
|
||||
configs = []
|
||||
for arg in args:
|
||||
configs.append(
|
||||
CustomRepository(
|
||||
arg['name'],
|
||||
arg['url'],
|
||||
SignCheck(arg['sign_check']),
|
||||
SignOption(arg['sign_option'])
|
||||
)
|
||||
arg["name"],
|
||||
arg["url"],
|
||||
SignCheck(arg["sign_check"]),
|
||||
SignOption(arg["sign_option"]),
|
||||
),
|
||||
)
|
||||
|
||||
return configs
|
||||
|
|
@ -214,17 +214,17 @@ class CustomServer:
|
|||
url: str
|
||||
|
||||
def table_data(self) -> dict[str, str]:
|
||||
return {'Url': self.url}
|
||||
return {"Url": self.url}
|
||||
|
||||
def json(self) -> dict[str, str]:
|
||||
return {'url': self.url}
|
||||
return {"url": self.url}
|
||||
|
||||
@classmethod
|
||||
def parse_args(cls, args: list[dict[str, str]]) -> list['CustomServer']:
|
||||
def parse_args(cls, args: list[dict[str, str]]) -> list["CustomServer"]:
|
||||
configs = []
|
||||
for arg in args:
|
||||
configs.append(
|
||||
CustomServer(arg['url'])
|
||||
CustomServer(arg["url"]),
|
||||
)
|
||||
|
||||
return configs
|
||||
|
|
@ -246,11 +246,11 @@ class MirrorConfiguration:
|
|||
|
||||
@property
|
||||
def region_names(self) -> str:
|
||||
return '\n'.join([m.name for m in self.mirror_regions])
|
||||
return "\n".join([m.name for m in self.mirror_regions])
|
||||
|
||||
@property
|
||||
def custom_server_urls(self) -> str:
|
||||
return '\n'.join([s.url for s in self.custom_servers])
|
||||
return "\n".join([s.url for s in self.custom_servers])
|
||||
|
||||
def json(self) -> _MirrorConfigurationSerialization:
|
||||
regions = {}
|
||||
|
|
@ -258,45 +258,45 @@ class MirrorConfiguration:
|
|||
regions.update(m.json())
|
||||
|
||||
return {
|
||||
'mirror_regions': regions,
|
||||
'custom_servers': self.custom_servers,
|
||||
'optional_repositories': [r.value for r in self.optional_repositories],
|
||||
'custom_repositories': [c.json() for c in self.custom_repositories]
|
||||
"mirror_regions": regions,
|
||||
"custom_servers": self.custom_servers,
|
||||
"optional_repositories": [r.value for r in self.optional_repositories],
|
||||
"custom_repositories": [c.json() for c in self.custom_repositories],
|
||||
}
|
||||
|
||||
def custom_servers_config(self) -> str:
|
||||
config = '## Custom Servers\n'
|
||||
config = "## Custom Servers\n"
|
||||
|
||||
for server in self.custom_servers:
|
||||
config += f'Server = {server.url}\n'
|
||||
config += f"Server = {server.url}\n"
|
||||
|
||||
return config.strip()
|
||||
|
||||
def regions_config(self, speed_sort: bool = True) -> str:
|
||||
from ..mirrors import mirror_list_handler
|
||||
|
||||
config = ''
|
||||
config = ""
|
||||
|
||||
for mirror_region in self.mirror_regions:
|
||||
sorted_stati = mirror_list_handler.get_status_by_region(
|
||||
mirror_region.name,
|
||||
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:
|
||||
config += f'Server = {status.server_url}\n'
|
||||
config += f"Server = {status.server_url}\n"
|
||||
|
||||
return config
|
||||
|
||||
def repositories_config(self) -> str:
|
||||
config = ''
|
||||
config = ""
|
||||
|
||||
for repo in self.custom_repositories:
|
||||
config += f'\n\n[{repo.name}]\n'
|
||||
config += f'SigLevel = {repo.sign_check.value} {repo.sign_option.value}\n'
|
||||
config += f'Server = {repo.url}\n'
|
||||
config += f"\n\n[{repo.name}]\n"
|
||||
config += f"SigLevel = {repo.sign_check.value} {repo.sign_option.value}\n"
|
||||
config += f"Server = {repo.url}\n"
|
||||
|
||||
return config
|
||||
|
||||
|
|
@ -304,26 +304,26 @@ class MirrorConfiguration:
|
|||
def parse_args(
|
||||
cls,
|
||||
args: dict[str, Any],
|
||||
backwards_compatible_repo: list[Repository] = []
|
||||
) -> 'MirrorConfiguration':
|
||||
backwards_compatible_repo: list[Repository] = [],
|
||||
) -> "MirrorConfiguration":
|
||||
config = MirrorConfiguration()
|
||||
|
||||
mirror_regions = args.get('mirror_regions', [])
|
||||
mirror_regions = args.get("mirror_regions", [])
|
||||
if mirror_regions:
|
||||
for region, urls in mirror_regions.items():
|
||||
config.mirror_regions.append(MirrorRegion(region, urls))
|
||||
|
||||
if args.get('custom_servers'):
|
||||
config.custom_servers = CustomServer.parse_args(args['custom_servers'])
|
||||
if args.get("custom_servers"):
|
||||
config.custom_servers = CustomServer.parse_args(args["custom_servers"])
|
||||
|
||||
# backwards compatibility with the new custom_repository
|
||||
if 'custom_mirrors' in args:
|
||||
config.custom_repositories = CustomRepository.parse_args(args['custom_mirrors'])
|
||||
if 'custom_repositories' in args:
|
||||
config.custom_repositories = CustomRepository.parse_args(args['custom_repositories'])
|
||||
if "custom_mirrors" in args:
|
||||
config.custom_repositories = CustomRepository.parse_args(args["custom_mirrors"])
|
||||
if "custom_repositories" in args:
|
||||
config.custom_repositories = CustomRepository.parse_args(args["custom_repositories"])
|
||||
|
||||
if 'optional_repositories' in args:
|
||||
config.optional_repositories = [Repository(r) for r in args['optional_repositories']]
|
||||
if "optional_repositories" in args:
|
||||
config.optional_repositories = [Repository(r) for r in args["optional_repositories"]]
|
||||
|
||||
if backwards_compatible_repo:
|
||||
for r in backwards_compatible_repo:
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ class NicType(Enum):
|
|||
def display_msg(self) -> str:
|
||||
match self:
|
||||
case NicType.ISO:
|
||||
return str(_('Copy ISO network configuration to installation'))
|
||||
return str(_("Copy ISO network configuration to installation"))
|
||||
case NicType.NM:
|
||||
return str(_('Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)'))
|
||||
return str(_("Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)"))
|
||||
case NicType.MANUAL:
|
||||
return str(_('Manual configuration'))
|
||||
return str(_("Manual configuration"))
|
||||
|
||||
|
||||
class _NicSerialization(TypedDict):
|
||||
|
|
@ -48,30 +48,30 @@ class Nic:
|
|||
|
||||
def table_data(self) -> dict[str, str | bool | list[str]]:
|
||||
return {
|
||||
'iface': self.iface if self.iface else '',
|
||||
'ip': self.ip if self.ip else '',
|
||||
'dhcp': self.dhcp,
|
||||
'gateway': self.gateway if self.gateway else '',
|
||||
'dns': self.dns
|
||||
"iface": self.iface if self.iface else "",
|
||||
"ip": self.ip if self.ip else "",
|
||||
"dhcp": self.dhcp,
|
||||
"gateway": self.gateway if self.gateway else "",
|
||||
"dns": self.dns,
|
||||
}
|
||||
|
||||
def json(self) -> _NicSerialization:
|
||||
return {
|
||||
'iface': self.iface,
|
||||
'ip': self.ip,
|
||||
'dhcp': self.dhcp,
|
||||
'gateway': self.gateway,
|
||||
'dns': self.dns
|
||||
"iface": self.iface,
|
||||
"ip": self.ip,
|
||||
"dhcp": self.dhcp,
|
||||
"gateway": self.gateway,
|
||||
"dns": self.dns,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def parse_arg(arg: _NicSerialization) -> Nic:
|
||||
return Nic(
|
||||
iface=arg.get('iface', None),
|
||||
ip=arg.get('ip', None),
|
||||
dhcp=arg.get('dhcp', True),
|
||||
gateway=arg.get('gateway', None),
|
||||
dns=arg.get('dns', []),
|
||||
iface=arg.get("iface", None),
|
||||
ip=arg.get("ip", None),
|
||||
dhcp=arg.get("dhcp", True),
|
||||
gateway=arg.get("gateway", None),
|
||||
dns=arg.get("dns", []),
|
||||
)
|
||||
|
||||
def as_systemd_config(self) -> str:
|
||||
|
|
@ -79,25 +79,25 @@ class Nic:
|
|||
network: list[tuple[str, str]] = []
|
||||
|
||||
if self.iface:
|
||||
match.append(('Name', self.iface))
|
||||
match.append(("Name", self.iface))
|
||||
|
||||
if self.dhcp:
|
||||
network.append(('DHCP', 'yes'))
|
||||
network.append(("DHCP", "yes"))
|
||||
else:
|
||||
if self.ip:
|
||||
network.append(('Address', self.ip))
|
||||
network.append(("Address", self.ip))
|
||||
if self.gateway:
|
||||
network.append(('Gateway', self.gateway))
|
||||
network.append(("Gateway", self.gateway))
|
||||
for dns in self.dns:
|
||||
network.append(('DNS', dns))
|
||||
network.append(("DNS", dns))
|
||||
|
||||
config = {'Match': match, 'Network': network}
|
||||
config = {"Match": match, "Network": network}
|
||||
|
||||
config_str = ''
|
||||
config_str = ""
|
||||
for top, entries in config.items():
|
||||
config_str += f'[{top}]\n'
|
||||
config_str += '\n'.join([f'{k}={v}' for k, v in entries])
|
||||
config_str += '\n\n'
|
||||
config_str += f"[{top}]\n"
|
||||
config_str += "\n".join([f"{k}={v}" for k, v in entries])
|
||||
config_str += "\n\n"
|
||||
|
||||
return config_str
|
||||
|
||||
|
|
@ -113,15 +113,15 @@ class NetworkConfiguration:
|
|||
nics: list[Nic] = field(default_factory=list)
|
||||
|
||||
def json(self) -> _NetworkConfigurationSerialization:
|
||||
config: _NetworkConfigurationSerialization = {'type': self.type.value}
|
||||
config: _NetworkConfigurationSerialization = {"type": self.type.value}
|
||||
if self.nics:
|
||||
config['nics'] = [n.json() for n in self.nics]
|
||||
config["nics"] = [n.json() for n in self.nics]
|
||||
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
def parse_arg(config: _NetworkConfigurationSerialization) -> NetworkConfiguration | None:
|
||||
nic_type = config.get('type', None)
|
||||
nic_type = config.get("type", None)
|
||||
if not nic_type:
|
||||
return None
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ class NetworkConfiguration:
|
|||
case NicType.NM:
|
||||
return NetworkConfiguration(NicType.NM)
|
||||
case NicType.MANUAL:
|
||||
nics_arg = config.get('nics', [])
|
||||
nics_arg = config.get("nics", [])
|
||||
if nics_arg:
|
||||
nics = [Nic.parse_arg(n) for n in nics_arg]
|
||||
return NetworkConfiguration(NicType.MANUAL, nics)
|
||||
|
|
@ -141,22 +141,22 @@ class NetworkConfiguration:
|
|||
def install_network_config(
|
||||
self,
|
||||
installation: Installer,
|
||||
profile_config: ProfileConfiguration | None = None
|
||||
profile_config: ProfileConfiguration | None = None,
|
||||
) -> None:
|
||||
match self.type:
|
||||
case NicType.ISO:
|
||||
installation.copy_iso_network_config(
|
||||
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:
|
||||
installation.add_additional_packages(["networkmanager"])
|
||||
if profile_config and profile_config.profile:
|
||||
if profile_config.profile.is_desktop_profile():
|
||||
installation.add_additional_packages(["network-manager-applet"])
|
||||
installation.enable_service('NetworkManager.service')
|
||||
installation.enable_service("NetworkManager.service")
|
||||
case NicType.MANUAL:
|
||||
for nic in self.nics:
|
||||
installation.configure_nic(nic)
|
||||
|
||||
installation.enable_service('systemd-networkd')
|
||||
installation.enable_service('systemd-resolved')
|
||||
installation.enable_service("systemd-networkd")
|
||||
installation.enable_service("systemd-resolved")
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class Repository(Enum):
|
||||
Core = 'core'
|
||||
Extra = 'extra'
|
||||
Multilib = 'multilib'
|
||||
Testing = 'testing'
|
||||
Core = "core"
|
||||
Extra = "extra"
|
||||
Multilib = "multilib"
|
||||
Testing = "testing"
|
||||
|
||||
def get_repository_list(self) -> list[str]:
|
||||
match self:
|
||||
|
|
@ -29,9 +29,9 @@ class Repository(Enum):
|
|||
return [Repository.Multilib.value]
|
||||
case Repository.Testing:
|
||||
return [
|
||||
'core-testing',
|
||||
'extra-testing',
|
||||
'multilib-testing'
|
||||
"core-testing",
|
||||
"extra-testing",
|
||||
"multilib-testing",
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ class PackageSearchResult:
|
|||
checkdepends: list[str]
|
||||
|
||||
@staticmethod
|
||||
def from_json(data: dict[str, Any]) -> 'PackageSearchResult':
|
||||
def from_json(data: dict[str, Any]) -> "PackageSearchResult":
|
||||
return PackageSearchResult(**data)
|
||||
|
||||
@property
|
||||
|
|
@ -79,7 +79,7 @@ class PackageSearchResult:
|
|||
|
||||
return self.pkg_version == other.pkg_version
|
||||
|
||||
def __lt__(self, other: 'PackageSearchResult') -> bool:
|
||||
def __lt__(self, other: "PackageSearchResult") -> bool:
|
||||
return self.pkg_version < other.pkg_version
|
||||
|
||||
|
||||
|
|
@ -93,16 +93,16 @@ class PackageSearch:
|
|||
results: list[PackageSearchResult]
|
||||
|
||||
@staticmethod
|
||||
def from_json(data: dict[str, Any]) -> 'PackageSearch':
|
||||
results = [PackageSearchResult.from_json(r) for r in data['results']]
|
||||
def from_json(data: dict[str, Any]) -> "PackageSearch":
|
||||
results = [PackageSearchResult.from_json(r) for r in data["results"]]
|
||||
|
||||
return PackageSearch(
|
||||
version=data['version'],
|
||||
limit=data['limit'],
|
||||
valid=data['valid'],
|
||||
num_pages=data['num_pages'],
|
||||
page=data['page'],
|
||||
results=results
|
||||
version=data["version"],
|
||||
limit=data["limit"],
|
||||
valid=data["valid"],
|
||||
num_pages=data["num_pages"],
|
||||
page=data["page"],
|
||||
results=results,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ class LocalPackage(BaseModel):
|
|||
|
||||
return self.version == other.version
|
||||
|
||||
def __lt__(self, other: 'LocalPackage') -> bool:
|
||||
def __lt__(self, other: "LocalPackage") -> bool:
|
||||
return self.version < other.version
|
||||
|
||||
|
||||
|
|
@ -166,11 +166,11 @@ class AvailablePackage(BaseModel):
|
|||
|
||||
# return all package info line by line
|
||||
def info(self) -> str:
|
||||
output = ''
|
||||
output = ""
|
||||
for key, value in self.model_dump().items():
|
||||
key = key.replace('_', ' ').capitalize()
|
||||
key = key.replace("_", " ").capitalize()
|
||||
key = key.ljust(self.longest_key)
|
||||
output += f'{key} : {value}\n'
|
||||
output += f"{key} : {value}\n"
|
||||
|
||||
return output
|
||||
|
||||
|
|
@ -183,15 +183,15 @@ class PackageGroup:
|
|||
@classmethod
|
||||
def from_available_packages(
|
||||
cls,
|
||||
packages: dict[str, AvailablePackage]
|
||||
) -> dict[str, 'PackageGroup']:
|
||||
pkg_groups: dict[str, 'PackageGroup'] = {}
|
||||
packages: dict[str, AvailablePackage],
|
||||
) -> dict[str, "PackageGroup"]:
|
||||
pkg_groups: dict[str, "PackageGroup"] = {}
|
||||
|
||||
for pkg in packages.values():
|
||||
if 'None' in pkg.groups:
|
||||
if "None" in pkg.groups:
|
||||
continue
|
||||
|
||||
groups = pkg.groups.split(' ')
|
||||
groups = pkg.groups.split(" ")
|
||||
|
||||
for group in groups:
|
||||
# same group names have multiple spaces in between
|
||||
|
|
@ -204,6 +204,6 @@ class PackageGroup:
|
|||
return pkg_groups
|
||||
|
||||
def info(self) -> str:
|
||||
output = str(_('Package group:')) + '\n - '
|
||||
output += '\n - '.join(self.packages)
|
||||
output = str(_("Package group:")) + "\n - "
|
||||
output += "\n - ".join(self.packages)
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -25,21 +25,23 @@ class ProfileConfiguration:
|
|||
|
||||
def json(self) -> _ProfileConfigurationSerialization:
|
||||
from ..profile.profiles_handler import profile_handler
|
||||
|
||||
return {
|
||||
'profile': profile_handler.to_json(self.profile),
|
||||
'gfx_driver': self.gfx_driver.value if self.gfx_driver else None,
|
||||
'greeter': self.greeter.value if self.greeter else None
|
||||
"profile": profile_handler.to_json(self.profile),
|
||||
"gfx_driver": self.gfx_driver.value if self.gfx_driver else None,
|
||||
"greeter": self.greeter.value if self.greeter else None,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def parse_arg(cls, arg: _ProfileConfigurationSerialization) -> 'ProfileConfiguration':
|
||||
def parse_arg(cls, arg: _ProfileConfigurationSerialization) -> "ProfileConfiguration":
|
||||
from ..profile.profiles_handler import profile_handler
|
||||
profile = profile_handler.parse_profile_config(arg['profile'])
|
||||
greeter = arg.get('greeter', None)
|
||||
gfx_driver = arg.get('gfx_driver', None)
|
||||
|
||||
profile = profile_handler.parse_profile_config(arg["profile"])
|
||||
greeter = arg.get("greeter", None)
|
||||
gfx_driver = arg.get("gfx_driver", None)
|
||||
|
||||
return ProfileConfiguration(
|
||||
profile,
|
||||
GfxDriver(gfx_driver) if gfx_driver else None,
|
||||
GreeterType(greeter) if greeter else None
|
||||
GreeterType(greeter) if greeter else None,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,37 +13,37 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class PasswordStrength(Enum):
|
||||
VERY_WEAK = 'very weak'
|
||||
WEAK = 'weak'
|
||||
MODERATE = 'moderate'
|
||||
STRONG = 'strong'
|
||||
VERY_WEAK = "very weak"
|
||||
WEAK = "weak"
|
||||
MODERATE = "moderate"
|
||||
STRONG = "strong"
|
||||
|
||||
@property
|
||||
@override
|
||||
def value(self) -> str: # pylint: disable=invalid-overridden-method
|
||||
match self:
|
||||
case PasswordStrength.VERY_WEAK:
|
||||
return str(_('very weak'))
|
||||
return str(_("very weak"))
|
||||
case PasswordStrength.WEAK:
|
||||
return str(_('weak'))
|
||||
return str(_("weak"))
|
||||
case PasswordStrength.MODERATE:
|
||||
return str(_('moderate'))
|
||||
return str(_("moderate"))
|
||||
case PasswordStrength.STRONG:
|
||||
return str(_('strong'))
|
||||
return str(_("strong"))
|
||||
|
||||
def color(self) -> str:
|
||||
match self:
|
||||
case PasswordStrength.VERY_WEAK:
|
||||
return 'red'
|
||||
return "red"
|
||||
case PasswordStrength.WEAK:
|
||||
return 'red'
|
||||
return "red"
|
||||
case PasswordStrength.MODERATE:
|
||||
return 'yellow'
|
||||
return "yellow"
|
||||
case PasswordStrength.STRONG:
|
||||
return 'green'
|
||||
return "green"
|
||||
|
||||
@classmethod
|
||||
def strength(cls, password: str) -> 'PasswordStrength':
|
||||
def strength(cls, password: str) -> "PasswordStrength":
|
||||
digit = any(character.isdigit() for character in password)
|
||||
upper = any(character.isupper() for character in password)
|
||||
lower = any(character.islower() for character in password)
|
||||
|
|
@ -57,8 +57,8 @@ class PasswordStrength(Enum):
|
|||
upper: bool,
|
||||
lower: bool,
|
||||
symbol: bool,
|
||||
length: int
|
||||
) -> 'PasswordStrength':
|
||||
length: int,
|
||||
) -> "PasswordStrength":
|
||||
# suggested evaluation
|
||||
# https://github.com/archlinux/archinstall/issues/1304#issuecomment-1146768163
|
||||
if digit and upper and lower and symbol:
|
||||
|
|
@ -106,28 +106,28 @@ class PasswordStrength(Enum):
|
|||
|
||||
|
||||
_UserSerialization = TypedDict(
|
||||
'_UserSerialization',
|
||||
"_UserSerialization",
|
||||
{
|
||||
'username': str,
|
||||
'!password': NotRequired[str],
|
||||
'sudo': bool,
|
||||
'groups': list[str],
|
||||
'enc_password': str | None
|
||||
}
|
||||
"username": str,
|
||||
"!password": NotRequired[str],
|
||||
"sudo": bool,
|
||||
"groups": list[str],
|
||||
"enc_password": str | None,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class Password:
|
||||
def __init__(
|
||||
self,
|
||||
plaintext: str = '',
|
||||
enc_password: str | None = None
|
||||
plaintext: str = "",
|
||||
enc_password: str | None = None,
|
||||
):
|
||||
if plaintext:
|
||||
enc_password = crypt_yescrypt(plaintext)
|
||||
|
||||
if not plaintext and not enc_password:
|
||||
raise ValueError('Either plaintext or enc_password must be provided')
|
||||
raise ValueError("Either plaintext or enc_password must be provided")
|
||||
|
||||
self._plaintext = plaintext
|
||||
self.enc_password = enc_password
|
||||
|
|
@ -153,9 +153,9 @@ class Password:
|
|||
|
||||
def hidden(self) -> str:
|
||||
if self._plaintext:
|
||||
return '*' * len(self._plaintext)
|
||||
return "*" * len(self._plaintext)
|
||||
else:
|
||||
return '*' * 8
|
||||
return "*" * 8
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -168,37 +168,37 @@ class User:
|
|||
@override
|
||||
def __str__(self) -> str:
|
||||
# safety overwrite to make sure password is not leaked
|
||||
return f'User({self.username=}, {self.sudo=}, {self.groups=})'
|
||||
return f"User({self.username=}, {self.sudo=}, {self.groups=})"
|
||||
|
||||
def table_data(self) -> dict[str, str | bool | list[str]]:
|
||||
return {
|
||||
'username': self.username,
|
||||
'password': self.password.hidden(),
|
||||
'sudo': self.sudo,
|
||||
'groups': self.groups
|
||||
"username": self.username,
|
||||
"password": self.password.hidden(),
|
||||
"sudo": self.sudo,
|
||||
"groups": self.groups,
|
||||
}
|
||||
|
||||
def json(self) -> _UserSerialization:
|
||||
return {
|
||||
'username': self.username,
|
||||
'enc_password': self.password.enc_password,
|
||||
'sudo': self.sudo,
|
||||
'groups': self.groups
|
||||
"username": self.username,
|
||||
"enc_password": self.password.enc_password,
|
||||
"sudo": self.sudo,
|
||||
"groups": self.groups,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def parse_arguments(
|
||||
cls,
|
||||
args: list[_UserSerialization]
|
||||
) -> list['User']:
|
||||
args: list[_UserSerialization],
|
||||
) -> list["User"]:
|
||||
users: list[User] = []
|
||||
|
||||
for entry in args:
|
||||
username = entry.get('username')
|
||||
username = entry.get("username")
|
||||
password: Password | None = None
|
||||
groups = entry.get('groups', [])
|
||||
plaintext = entry.get('!password')
|
||||
enc_password = entry.get('enc_password')
|
||||
groups = entry.get("groups", [])
|
||||
plaintext = entry.get("!password")
|
||||
enc_password = entry.get("enc_password")
|
||||
|
||||
# DEPRECATED: backwards compatibility
|
||||
if plaintext:
|
||||
|
|
@ -212,8 +212,8 @@ class User:
|
|||
user = User(
|
||||
username=username,
|
||||
password=password,
|
||||
sudo=entry.get('sudo', False) is True,
|
||||
groups=groups
|
||||
sudo=entry.get("sudo", False) is True,
|
||||
groups=groups,
|
||||
)
|
||||
|
||||
users.append(user)
|
||||
|
|
|
|||
|
|
@ -18,16 +18,17 @@ from .pacman import Pacman
|
|||
|
||||
|
||||
class DownloadTimer:
|
||||
'''
|
||||
"""
|
||||
Context manager for timing downloads with timeouts.
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, timeout: int = 5):
|
||||
'''
|
||||
"""
|
||||
Args:
|
||||
timeout:
|
||||
The download timeout in seconds. The DownloadTimeout exception
|
||||
will be raised in the context after this many seconds.
|
||||
'''
|
||||
"""
|
||||
self.time: float | None = None
|
||||
self.start_time: float | None = None
|
||||
self.timeout = timeout
|
||||
|
|
@ -35,10 +36,10 @@ class DownloadTimer:
|
|||
self.previous_timer: int | None = None
|
||||
|
||||
def raise_timeout(self, signl: int, frame: FrameType | None) -> None:
|
||||
'''
|
||||
"""
|
||||
Raise the DownloadTimeout exception.
|
||||
'''
|
||||
raise DownloadTimeout(f'Download timed out after {self.timeout} second(s).')
|
||||
"""
|
||||
raise DownloadTimeout(f"Download timed out after {self.timeout} second(s).")
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
if self.timeout > 0:
|
||||
|
|
@ -69,9 +70,10 @@ class DownloadTimer:
|
|||
|
||||
def get_hw_addr(ifname: str) -> str:
|
||||
import fcntl
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
ret = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', bytes(ifname, 'utf-8')[:15]))
|
||||
return ':'.join(f'{b:02x}' for b in ret[18:24])
|
||||
ret = fcntl.ioctl(s.fileno(), 0x8927, struct.pack("256s", bytes(ifname, "utf-8")[:15]))
|
||||
return ":".join(f"{b:02x}" for b in ret[18:24])
|
||||
|
||||
|
||||
def list_interfaces(skip_loopback: bool = True) -> dict[str, str]:
|
||||
|
|
@ -81,7 +83,7 @@ def list_interfaces(skip_loopback: bool = True) -> dict[str, str]:
|
|||
if skip_loopback and iface == "lo":
|
||||
continue
|
||||
|
||||
mac = get_hw_addr(iface).replace(':', '-').lower()
|
||||
mac = get_hw_addr(iface).replace(":", "-").lower()
|
||||
interfaces[mac] = iface
|
||||
|
||||
return interfaces
|
||||
|
|
@ -104,17 +106,17 @@ def enrich_iface_types(interfaces: list[str]) -> dict[str, str]:
|
|||
|
||||
for iface in interfaces:
|
||||
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"):
|
||||
# ethtool -i {iface}
|
||||
result[iface] = 'TUN/TAP'
|
||||
result[iface] = "TUN/TAP"
|
||||
elif os.path.isdir(f"/sys/class/net/{iface}/device"):
|
||||
if os.path.isdir(f"/sys/class/net/{iface}/wireless/"):
|
||||
result[iface] = 'WIRELESS'
|
||||
result[iface] = "WIRELESS"
|
||||
else:
|
||||
result[iface] = 'PHYSICAL'
|
||||
result[iface] = "PHYSICAL"
|
||||
else:
|
||||
result[iface] = 'UNKNOWN'
|
||||
result[iface] = "UNKNOWN"
|
||||
|
||||
return result
|
||||
|
||||
|
|
@ -126,28 +128,25 @@ def fetch_data_from_url(url: str, params: dict[str, str] | None = None) -> str:
|
|||
|
||||
if params is not None:
|
||||
encoded = urlencode(params)
|
||||
full_url = f'{url}?{encoded}'
|
||||
full_url = f"{url}?{encoded}"
|
||||
else:
|
||||
full_url = url
|
||||
|
||||
try:
|
||||
response = urlopen(full_url, context=ssl_context)
|
||||
data = response.read().decode('UTF-8')
|
||||
data = response.read().decode("UTF-8")
|
||||
return data
|
||||
except URLError as e:
|
||||
raise ValueError(f'Unable to fetch data from url: {url}\n{e}')
|
||||
raise ValueError(f"Unable to fetch data from url: {url}\n{e}")
|
||||
except Exception as e:
|
||||
raise ValueError(f'Unexpected error when parsing response: {e}')
|
||||
raise ValueError(f"Unexpected error when parsing response: {e}")
|
||||
|
||||
|
||||
def calc_checksum(icmp_packet: bytes) -> int:
|
||||
# Calculate the ICMP checksum
|
||||
checksum = 0
|
||||
for i in range(0, len(icmp_packet), 2):
|
||||
checksum += (icmp_packet[i] << 8) + (
|
||||
struct.unpack('B', icmp_packet[i + 1:i + 2])[0]
|
||||
if len(icmp_packet[i + 1:i + 2]) else 0
|
||||
)
|
||||
checksum += (icmp_packet[i] << 8) + (struct.unpack("B", icmp_packet[i + 1 : i + 2])[0] if len(icmp_packet[i + 1 : i + 2]) else 0)
|
||||
|
||||
checksum = (checksum >> 16) + (checksum & 0xFFFF)
|
||||
checksum = ~checksum & 0xFFFF
|
||||
|
|
@ -157,17 +156,17 @@ def calc_checksum(icmp_packet: bytes) -> int:
|
|||
|
||||
def build_icmp(payload: bytes) -> bytes:
|
||||
# Define the ICMP Echo Request packet
|
||||
icmp_packet = struct.pack('!BBHHH', 8, 0, 0, 0, 1) + payload
|
||||
icmp_packet = struct.pack("!BBHHH", 8, 0, 0, 0, 1) + payload
|
||||
|
||||
checksum = calc_checksum(icmp_packet)
|
||||
|
||||
return struct.pack('!BBHHH', 8, 0, checksum, 0, 1) + payload
|
||||
return struct.pack("!BBHHH", 8, 0, checksum, 0, 1) + payload
|
||||
|
||||
|
||||
def ping(hostname, timeout: int = 5) -> int:
|
||||
watchdog = select.epoll()
|
||||
started = time.time()
|
||||
random_identifier = f'archinstall-{random.randint(1000, 9999)}'.encode()
|
||||
random_identifier = f"archinstall-{random.randint(1000, 9999)}".encode()
|
||||
|
||||
# Create a raw socket (requires root, which should be fine on archiso)
|
||||
icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
|
||||
|
|
@ -185,10 +184,10 @@ def ping(hostname, timeout: int = 5) -> int:
|
|||
try:
|
||||
for _fileno, _event in watchdog.poll(0.1):
|
||||
response, _ = icmp_socket.recvfrom(1024)
|
||||
icmp_type = struct.unpack('!B', response[20:21])[0]
|
||||
icmp_type = struct.unpack("!B", response[20:21])[0]
|
||||
|
||||
# Check if it's an Echo Reply (ICMP type 0)
|
||||
if icmp_type == 0 and response[-len(random_identifier):] == random_identifier:
|
||||
if icmp_type == 0 and response[-len(random_identifier) :] == random_identifier:
|
||||
latency = round((time.time() - started) * 1000)
|
||||
break
|
||||
except OSError as e:
|
||||
|
|
|
|||
|
|
@ -16,13 +16,12 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class FormattedOutput:
|
||||
|
||||
@classmethod
|
||||
def _get_values(
|
||||
cls,
|
||||
o: 'DataclassInstance',
|
||||
o: "DataclassInstance",
|
||||
class_formatter: str | Callable | None = None, # type: ignore[type-arg]
|
||||
filter_list: list[str] = []
|
||||
filter_list: list[str] = [],
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
the original values returned a dataclass as dict thru the call to some specific methods
|
||||
|
|
@ -39,10 +38,10 @@ class FormattedOutput:
|
|||
func = getattr(o, class_formatter)
|
||||
return func(filter_list)
|
||||
|
||||
raise ValueError('Unsupported formatting call')
|
||||
elif hasattr(o, 'table_data'):
|
||||
raise ValueError("Unsupported formatting call")
|
||||
elif hasattr(o, "table_data"):
|
||||
return o.table_data()
|
||||
elif hasattr(o, 'json'):
|
||||
elif hasattr(o, "json"):
|
||||
return o.json()
|
||||
elif is_dataclass(o):
|
||||
return asdict(o)
|
||||
|
|
@ -55,9 +54,9 @@ class FormattedOutput:
|
|||
obj: list[Any],
|
||||
class_formatter: str | Callable | None = None, # type: ignore[type-arg]
|
||||
filter_list: list[str] = [],
|
||||
capitalize: bool = False
|
||||
capitalize: bool = False,
|
||||
) -> str:
|
||||
""" variant of as_table (subtly different code) which has two additional parameters
|
||||
"""variant of as_table (subtly different code) which has two additional parameters
|
||||
filter which is a list of fields which will be shon
|
||||
class_formatter a special method to format the outgoing data
|
||||
|
||||
|
|
@ -79,36 +78,36 @@ class FormattedOutput:
|
|||
filter_list = list(column_width.keys())
|
||||
|
||||
# create the header lines
|
||||
output = ''
|
||||
output = ""
|
||||
key_list = []
|
||||
for key in filter_list:
|
||||
width = column_width[key]
|
||||
key = key.replace('!', '').replace('_', ' ')
|
||||
key = key.replace("!", "").replace("_", " ")
|
||||
|
||||
if capitalize:
|
||||
key = key.capitalize()
|
||||
|
||||
key_list.append(unicode_ljust(key, width))
|
||||
|
||||
output += ' | '.join(key_list) + '\n'
|
||||
output += '-' * len(output) + '\n'
|
||||
output += " | ".join(key_list) + "\n"
|
||||
output += "-" * len(output) + "\n"
|
||||
|
||||
# create the data lines
|
||||
for record in raw_data:
|
||||
obj_data = []
|
||||
for key in filter_list:
|
||||
width = column_width.get(key, len(key))
|
||||
value = record.get(key, '')
|
||||
value = record.get(key, "")
|
||||
|
||||
if '!' in key:
|
||||
value = '*' * len(value)
|
||||
if "!" in key:
|
||||
value = "*" * len(value)
|
||||
|
||||
if isinstance(value, int | float) or (isinstance(value, str) and value.isnumeric()):
|
||||
obj_data.append(unicode_rjust(str(value), width))
|
||||
else:
|
||||
obj_data.append(unicode_ljust(str(value), width))
|
||||
|
||||
output += ' | '.join(obj_data) + '\n'
|
||||
output += " | ".join(obj_data) + "\n"
|
||||
|
||||
return output
|
||||
|
||||
|
|
@ -118,14 +117,14 @@ class FormattedOutput:
|
|||
Will format a list into a given number of columns
|
||||
"""
|
||||
chunks = []
|
||||
output = ''
|
||||
output = ""
|
||||
|
||||
for i in range(0, len(entries), cols):
|
||||
chunks.append(entries[i:i + cols])
|
||||
chunks.append(entries[i : i + cols])
|
||||
|
||||
for row in chunks:
|
||||
out_fmt = '{: <30} ' * len(row)
|
||||
output += out_fmt.format(*row) + '\n'
|
||||
out_fmt = "{: <30} " * len(row)
|
||||
output += out_fmt.format(*row) + "\n"
|
||||
|
||||
return output
|
||||
|
||||
|
|
@ -138,7 +137,7 @@ class Journald:
|
|||
except ModuleNotFoundError:
|
||||
return None
|
||||
|
||||
log_adapter = logging.getLogger('archinstall')
|
||||
log_adapter = logging.getLogger("archinstall")
|
||||
log_fmt = logging.Formatter("[%(levelname)s]: %(message)s")
|
||||
log_ch = systemd.journal.JournalHandler()
|
||||
log_ch.setFormatter(log_fmt)
|
||||
|
|
@ -149,11 +148,11 @@ class Journald:
|
|||
|
||||
|
||||
def _check_log_permissions() -> None:
|
||||
filename = storage.get('LOG_FILE', None)
|
||||
log_dir = storage.get('LOG_PATH', Path('./'))
|
||||
filename = storage.get("LOG_FILE", None)
|
||||
log_dir = storage.get("LOG_PATH", Path("./"))
|
||||
|
||||
if not filename:
|
||||
raise ValueError('No log file name defined')
|
||||
raise ValueError("No log file name defined")
|
||||
|
||||
log_file = log_dir / filename
|
||||
|
||||
|
|
@ -161,17 +160,17 @@ def _check_log_permissions() -> None:
|
|||
log_dir.mkdir(exist_ok=True, parents=True)
|
||||
log_file.touch(exist_ok=True)
|
||||
|
||||
with log_file.open('a') as fp:
|
||||
fp.write('')
|
||||
with log_file.open("a") as fp:
|
||||
fp.write("")
|
||||
except PermissionError:
|
||||
# Fallback to creating the log file in the current folder
|
||||
fallback_dir = Path('./').absolute()
|
||||
fallback_dir = Path("./").absolute()
|
||||
fallback_log_file = fallback_dir / filename
|
||||
|
||||
fallback_log_file.touch(exist_ok=True)
|
||||
|
||||
storage['LOG_PATH'] = fallback_dir
|
||||
warn(f'Not enough permission to place log file at {log_file}, creating it in {fallback_log_file} instead')
|
||||
storage["LOG_PATH"] = fallback_dir
|
||||
warn(f"Not enough permission to place log file at {log_file}, creating it in {fallback_log_file} instead")
|
||||
|
||||
|
||||
def _supports_color() -> bool:
|
||||
|
|
@ -184,20 +183,20 @@ def _supports_color() -> bool:
|
|||
Return True if the running system's terminal supports color,
|
||||
and False otherwise.
|
||||
"""
|
||||
supported_platform = sys.platform != 'win32' or 'ANSICON' in os.environ
|
||||
supported_platform = sys.platform != "win32" or "ANSICON" in os.environ
|
||||
|
||||
# isatty is not always implemented, #6223.
|
||||
is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
|
||||
is_a_tty = hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
|
||||
return supported_platform and is_a_tty
|
||||
|
||||
|
||||
class Font(Enum):
|
||||
bold = '1'
|
||||
italic = '3'
|
||||
underscore = '4'
|
||||
blink = '5'
|
||||
reverse = '7'
|
||||
conceal = '8'
|
||||
bold = "1"
|
||||
italic = "3"
|
||||
underscore = "4"
|
||||
blink = "5"
|
||||
reverse = "7"
|
||||
conceal = "8"
|
||||
|
||||
|
||||
def _stylize_output(
|
||||
|
|
@ -216,29 +215,29 @@ def _stylize_output(
|
|||
Adds styling to a text given a set of color arguments.
|
||||
"""
|
||||
colors = {
|
||||
'black': '0',
|
||||
'red': '1',
|
||||
'green': '2',
|
||||
'yellow': '3',
|
||||
'blue': '4',
|
||||
'magenta': '5',
|
||||
'cyan': '6',
|
||||
'white': '7',
|
||||
'teal': '8;5;109', # Extended 256-bit colors (not always supported)
|
||||
'orange': '8;5;208', # https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
|
||||
'darkorange': '8;5;202',
|
||||
'gray': '8;5;246',
|
||||
'grey': '8;5;246',
|
||||
'darkgray': '8;5;240',
|
||||
'lightgray': '8;5;256'
|
||||
"black": "0",
|
||||
"red": "1",
|
||||
"green": "2",
|
||||
"yellow": "3",
|
||||
"blue": "4",
|
||||
"magenta": "5",
|
||||
"cyan": "6",
|
||||
"white": "7",
|
||||
"teal": "8;5;109", # Extended 256-bit colors (not always supported)
|
||||
"orange": "8;5;208", # https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
|
||||
"darkorange": "8;5;202",
|
||||
"gray": "8;5;246",
|
||||
"grey": "8;5;246",
|
||||
"darkgray": "8;5;240",
|
||||
"lightgray": "8;5;256",
|
||||
}
|
||||
|
||||
foreground = {key: f'3{colors[key]}' for key in colors}
|
||||
background = {key: f'4{colors[key]}' for key in colors}
|
||||
foreground = {key: f"3{colors[key]}" for key in colors}
|
||||
background = {key: f"4{colors[key]}" for key in colors}
|
||||
code_list = []
|
||||
|
||||
if text == '' and reset:
|
||||
return '\x1b[0m'
|
||||
if text == "" and reset:
|
||||
return "\x1b[0m"
|
||||
|
||||
code_list.append(foreground[str(fg)])
|
||||
|
||||
|
|
@ -248,34 +247,34 @@ def _stylize_output(
|
|||
for o in font:
|
||||
code_list.append(o.value)
|
||||
|
||||
ansi = ';'.join(code_list)
|
||||
ansi = ";".join(code_list)
|
||||
|
||||
return f'\033[{ansi}m{text}\033[0m'
|
||||
return f"\033[{ansi}m{text}\033[0m"
|
||||
|
||||
|
||||
def info(
|
||||
*msgs: str,
|
||||
level: int = logging.INFO,
|
||||
fg: str = 'white',
|
||||
fg: str = "white",
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = []
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
||||
def _timestamp() -> str:
|
||||
now = datetime.now(tz=UTC)
|
||||
return now.strftime('%Y-%m-%d %H:%M:%S')
|
||||
return now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def debug(
|
||||
*msgs: str,
|
||||
level: int = logging.DEBUG,
|
||||
fg: str = 'white',
|
||||
fg: str = "white",
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = []
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
|
@ -283,10 +282,10 @@ def debug(
|
|||
def error(
|
||||
*msgs: str,
|
||||
level: int = logging.ERROR,
|
||||
fg: str = 'red',
|
||||
fg: str = "red",
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = []
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
|
@ -294,10 +293,10 @@ def error(
|
|||
def warn(
|
||||
*msgs: str,
|
||||
level: int = logging.WARNING,
|
||||
fg: str = 'yellow',
|
||||
fg: str = "yellow",
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = []
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
|
@ -305,25 +304,25 @@ def warn(
|
|||
def log(
|
||||
*msgs: str,
|
||||
level: int = logging.INFO,
|
||||
fg: str = 'white',
|
||||
fg: str = "white",
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = []
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
# leave this check here as we need to setup the logging
|
||||
# right from the beginning when the modules are loaded
|
||||
_check_log_permissions()
|
||||
|
||||
text = orig_string = ' '.join([str(x) for x in msgs])
|
||||
text = orig_string = " ".join([str(x) for x in msgs])
|
||||
|
||||
# Attempt to colorize the output if supported
|
||||
# Insert default colors and override with **kwargs
|
||||
if _supports_color():
|
||||
text = _stylize_output(text, fg, bg, reset, font)
|
||||
|
||||
log_file = storage['LOG_PATH'] / storage['LOG_FILE']
|
||||
log_file = storage["LOG_PATH"] / storage["LOG_FILE"]
|
||||
|
||||
with log_file.open('a') as fp:
|
||||
with log_file.open("a") as fp:
|
||||
ts = _timestamp()
|
||||
level_name = logging.getLevelName(level)
|
||||
out = f"[{ts}] - {level_name} - {orig_string}\n"
|
||||
|
|
@ -333,4 +332,5 @@ def log(
|
|||
|
||||
if level != logging.DEBUG:
|
||||
from archinstall.tui.curses_menu import Tui
|
||||
|
||||
Tui.print(text)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from .packages import find_package, find_packages, group_search, installed_package, list_available_packages, package_search, validate_package_list
|
||||
|
||||
__all__ = [
|
||||
'find_package',
|
||||
'find_packages',
|
||||
'group_search',
|
||||
'installed_package',
|
||||
'list_available_packages',
|
||||
'package_search',
|
||||
'validate_package_list',
|
||||
"find_package",
|
||||
"find_packages",
|
||||
"group_search",
|
||||
"installed_package",
|
||||
"list_available_packages",
|
||||
"package_search",
|
||||
"validate_package_list",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ from ..models.packages import AvailablePackage, LocalPackage, PackageSearch, Pac
|
|||
from ..output import debug
|
||||
from ..pacman import Pacman
|
||||
|
||||
BASE_URL_PKG_SEARCH = 'https://archlinux.org/packages/search/json/'
|
||||
BASE_URL_PKG_SEARCH = "https://archlinux.org/packages/search/json/"
|
||||
# BASE_URL_PKG_CONTENT = 'https://archlinux.org/packages/search/json/'
|
||||
BASE_GROUP_URL = 'https://archlinux.org/groups/search/json/'
|
||||
BASE_GROUP_URL = "https://archlinux.org/groups/search/json/"
|
||||
|
||||
|
||||
def _make_request(url: str, params: dict[str, str]) -> addinfourl:
|
||||
|
|
@ -22,7 +22,7 @@ def _make_request(url: str, params: dict[str, str]) -> addinfourl:
|
|||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
encoded = urlencode(params)
|
||||
full_url = f'{url}?{encoded}'
|
||||
full_url = f"{url}?{encoded}"
|
||||
|
||||
return urlopen(full_url, context=ssl_context)
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ def _make_request(url: str, params: dict[str, str]) -> addinfourl:
|
|||
def group_search(name: str) -> list[PackageSearchResult]:
|
||||
# TODO UPSTREAM: Implement /json/ for the groups search
|
||||
try:
|
||||
response = _make_request(BASE_GROUP_URL, {'name': name})
|
||||
response = _make_request(BASE_GROUP_URL, {"name": name})
|
||||
except HTTPError as err:
|
||||
if err.code == 404:
|
||||
return []
|
||||
|
|
@ -38,9 +38,9 @@ def group_search(name: str) -> list[PackageSearchResult]:
|
|||
raise err
|
||||
|
||||
# Just to be sure some code didn't slip through the exception
|
||||
data = response.read().decode('utf-8')
|
||||
data = response.read().decode("utf-8")
|
||||
|
||||
return [PackageSearchResult(**package) for package in json.loads(data)['results']]
|
||||
return [PackageSearchResult(**package) for package in json.loads(data)["results"]]
|
||||
|
||||
|
||||
def package_search(package: str) -> PackageSearch:
|
||||
|
|
@ -50,12 +50,12 @@ def package_search(package: str) -> PackageSearch:
|
|||
"""
|
||||
# TODO UPSTREAM: Implement bulk search, either support name=X&name=Y or split on space (%20 or ' ')
|
||||
# TODO: utilize pacman cache first, upstream second.
|
||||
response = _make_request(BASE_URL_PKG_SEARCH, {'name': package})
|
||||
response = _make_request(BASE_URL_PKG_SEARCH, {"name": package})
|
||||
|
||||
if response.code != 200:
|
||||
raise PackageError(f"Could not locate package: [{response.code}] {response}")
|
||||
|
||||
data = response.read().decode('UTF-8')
|
||||
data = response.read().decode("UTF-8")
|
||||
json_data = json.loads(data)
|
||||
return PackageSearch.from_json(json_data)
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ def validate_package_list(packages: list[str]) -> tuple[list[str], list[str]]:
|
|||
def installed_package(package: str) -> LocalPackage | None:
|
||||
package_info = []
|
||||
try:
|
||||
package_info = Pacman.run(f'-Q --info {package}').decode().split('\n')
|
||||
package_info = Pacman.run(f"-Q --info {package}").decode().split("\n")
|
||||
return _parse_package_output(package_info, LocalPackage)
|
||||
except SysCallError:
|
||||
pass
|
||||
|
|
@ -117,7 +117,7 @@ def installed_package(package: str) -> LocalPackage | None:
|
|||
|
||||
@lru_cache
|
||||
def list_available_packages(
|
||||
repositories: tuple[Repository]
|
||||
repositories: tuple[Repository],
|
||||
) -> dict[str, AvailablePackage]:
|
||||
"""
|
||||
Returns a list of all available packages in the database
|
||||
|
|
@ -129,13 +129,13 @@ def list_available_packages(
|
|||
try:
|
||||
Pacman.run("-Sy")
|
||||
except Exception as e:
|
||||
debug(f'Failed to sync Arch Linux package database: {e}')
|
||||
debug(f"Failed to sync Arch Linux package database: {e}")
|
||||
|
||||
for line in Pacman.run('-S --info'):
|
||||
for line in Pacman.run("-S --info"):
|
||||
dec_line = line.decode().strip()
|
||||
current_package.append(dec_line)
|
||||
|
||||
if dec_line.startswith('Validated'):
|
||||
if dec_line.startswith("Validated"):
|
||||
if current_package:
|
||||
avail_pkg = _parse_package_output(current_package, AvailablePackage)
|
||||
if avail_pkg.repository in filtered_repos:
|
||||
|
|
@ -147,18 +147,18 @@ def list_available_packages(
|
|||
|
||||
@lru_cache(maxsize=128)
|
||||
def _normalize_key_name(key: str) -> str:
|
||||
return key.strip().lower().replace(' ', '_')
|
||||
return key.strip().lower().replace(" ", "_")
|
||||
|
||||
|
||||
def _parse_package_output[PackageType: (AvailablePackage, LocalPackage)](
|
||||
package_meta: list[str],
|
||||
cls: type[PackageType]
|
||||
cls: type[PackageType],
|
||||
) -> PackageType:
|
||||
package = {}
|
||||
|
||||
for line in package_meta:
|
||||
if ':' in line:
|
||||
key, value = line.split(':', 1)
|
||||
if ":" in line:
|
||||
key, value = line.split(":", 1)
|
||||
key = _normalize_key_name(key)
|
||||
package[key] = value.strip()
|
||||
|
||||
|
|
|
|||
|
|
@ -16,33 +16,32 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class Pacman:
|
||||
|
||||
def __init__(self, target: Path, silent: bool = False):
|
||||
self.synced = False
|
||||
self.silent = silent
|
||||
self.target = target
|
||||
|
||||
@staticmethod
|
||||
def run(args: str, default_cmd: str = 'pacman') -> SysCommand:
|
||||
def run(args: str, default_cmd: str = "pacman") -> SysCommand:
|
||||
"""
|
||||
A centralized function to call `pacman` from.
|
||||
It also protects us from colliding with other running pacman sessions (if used locally).
|
||||
The grace period is set to 10 minutes before exiting hard if another pacman instance is running.
|
||||
"""
|
||||
pacman_db_lock = Path('/var/lib/pacman/db.lck')
|
||||
pacman_db_lock = Path("/var/lib/pacman/db.lck")
|
||||
|
||||
if pacman_db_lock.exists():
|
||||
warn(str(_('Pacman is already running, waiting maximum 10 minutes for it to terminate.')))
|
||||
warn(str(_("Pacman is already running, waiting maximum 10 minutes for it to terminate.")))
|
||||
|
||||
started = time.time()
|
||||
while pacman_db_lock.exists():
|
||||
time.sleep(0.25)
|
||||
|
||||
if time.time() - started > (60 * 10):
|
||||
error(str(_('Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.')))
|
||||
error(str(_("Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.")))
|
||||
exit(1)
|
||||
|
||||
return SysCommand(f'{default_cmd} {args}')
|
||||
return SysCommand(f"{default_cmd} {args}")
|
||||
|
||||
def ask(self, error_message: str, bail_message: str, func: Callable, *args, **kwargs) -> None: # type: ignore[type-arg]
|
||||
while True:
|
||||
|
|
@ -50,20 +49,20 @@ class Pacman:
|
|||
func(*args, **kwargs)
|
||||
break
|
||||
except Exception as err:
|
||||
error(f'{error_message}: {err}')
|
||||
if not self.silent and input('Would you like to re-try this download? (Y/n): ').lower().strip() in 'y':
|
||||
error(f"{error_message}: {err}")
|
||||
if not self.silent and input("Would you like to re-try this download? (Y/n): ").lower().strip() in "y":
|
||||
continue
|
||||
raise RequirementError(f'{bail_message}: {err}')
|
||||
raise RequirementError(f"{bail_message}: {err}")
|
||||
|
||||
def sync(self) -> None:
|
||||
if self.synced:
|
||||
return
|
||||
self.ask(
|
||||
'Could not sync a new package database',
|
||||
'Could not sync mirrors',
|
||||
"Could not sync a new package database",
|
||||
"Could not sync mirrors",
|
||||
self.run,
|
||||
'-Syy',
|
||||
default_cmd='pacman'
|
||||
"-Syy",
|
||||
default_cmd="pacman",
|
||||
)
|
||||
self.synced = True
|
||||
|
||||
|
|
@ -73,22 +72,22 @@ class Pacman:
|
|||
packages = [packages]
|
||||
|
||||
for plugin in plugins.values():
|
||||
if hasattr(plugin, 'on_pacstrap'):
|
||||
if (result := plugin.on_pacstrap(packages)):
|
||||
if hasattr(plugin, "on_pacstrap"):
|
||||
if result := plugin.on_pacstrap(packages):
|
||||
packages = result
|
||||
|
||||
info(f'Installing packages: {packages}')
|
||||
info(f"Installing packages: {packages}")
|
||||
|
||||
self.ask(
|
||||
'Could not strap in packages',
|
||||
'Pacstrap failed. See /var/log/archinstall/install.log or above message for error details',
|
||||
"Could not strap in packages",
|
||||
"Pacstrap failed. See /var/log/archinstall/install.log or above message for error details",
|
||||
SysCommand,
|
||||
f'pacstrap -C /etc/pacman.conf -K {self.target} {" ".join(packages)} --noconfirm',
|
||||
peek_output=True
|
||||
f"pacstrap -C /etc/pacman.conf -K {self.target} {' '.join(packages)} --noconfirm",
|
||||
peek_output=True,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'Pacman',
|
||||
'PacmanConfig',
|
||||
"Pacman",
|
||||
"PacmanConfig",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class PacmanConfig:
|
|||
repos_to_enable = []
|
||||
for repo in self._repositories:
|
||||
if repo == Repository.Testing:
|
||||
repos_to_enable.extend(['core-testing', 'extra-testing', 'multilib-testing'])
|
||||
repos_to_enable.extend(["core-testing", "extra-testing", "multilib-testing"])
|
||||
else:
|
||||
repos_to_enable.append(repo.value)
|
||||
|
||||
|
|
@ -35,18 +35,18 @@ class PacmanConfig:
|
|||
|
||||
for row, line in enumerate(content):
|
||||
# Check if this is a commented repository section that needs to be enabled
|
||||
match = re.match(r'^#\s*\[(.*)\]', line)
|
||||
match = re.match(r"^#\s*\[(.*)\]", line)
|
||||
|
||||
if match and match.group(1) in repos_to_enable:
|
||||
# uncomment the repository section line, properly removing # and any spaces
|
||||
content[row] = re.sub(r'^#\s*', '', line)
|
||||
content[row] = re.sub(r"^#\s*", "", line)
|
||||
|
||||
# also uncomment the next line (Include statement) if it exists and is commented
|
||||
if row + 1 < len(content) and content[row + 1].lstrip().startswith('#'):
|
||||
content[row + 1] = re.sub(r'^#\s*', '', content[row + 1])
|
||||
if row + 1 < len(content) and content[row + 1].lstrip().startswith("#"):
|
||||
content[row + 1] = re.sub(r"^#\s*", "", content[row + 1])
|
||||
|
||||
# Write the modified content back to the file
|
||||
with open(self._config_path, 'w') as f:
|
||||
with open(self._config_path, "w") as f:
|
||||
f.writelines(content)
|
||||
|
||||
def persist(self) -> None:
|
||||
|
|
|
|||
|
|
@ -16,15 +16,15 @@ plugins = {}
|
|||
# 1: List archinstall.plugin definitions
|
||||
# 2: Load the plugin entrypoint
|
||||
# 3: Initiate the plugin and store it as .name in plugins
|
||||
for plugin_definition in metadata.entry_points().select(group='archinstall.plugin'):
|
||||
for plugin_definition in metadata.entry_points().select(group="archinstall.plugin"):
|
||||
plugin_entrypoint = plugin_definition.load()
|
||||
|
||||
try:
|
||||
plugins[plugin_definition.name] = plugin_entrypoint()
|
||||
except Exception as err:
|
||||
error(
|
||||
f'Error: {err}',
|
||||
f"The above error was detected when loading the plugin: {plugin_definition}"
|
||||
f"Error: {err}",
|
||||
f"The above error was detected when loading the plugin: {plugin_definition}",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -34,11 +34,11 @@ def _localize_path(path: Path) -> Path:
|
|||
"""
|
||||
url = urllib.parse.urlparse(str(path))
|
||||
|
||||
if url.scheme and url.scheme in ('https', 'http'):
|
||||
converted_path = Path(f'/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py')
|
||||
if url.scheme and url.scheme in ("https", "http"):
|
||||
converted_path = Path(f"/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py")
|
||||
|
||||
with open(converted_path, "w") as temp_file:
|
||||
temp_file.write(urllib.request.urlopen(url.geturl()).read().decode('utf-8'))
|
||||
temp_file.write(urllib.request.urlopen(url.geturl()).read().decode("utf-8"))
|
||||
|
||||
return converted_path
|
||||
else:
|
||||
|
|
@ -49,7 +49,7 @@ def _import_via_path(path: Path, namespace: str | None = None) -> str | None:
|
|||
if not namespace:
|
||||
namespace = os.path.basename(path)
|
||||
|
||||
if namespace == '__init__.py':
|
||||
if namespace == "__init__.py":
|
||||
namespace = path.parent.name
|
||||
|
||||
try:
|
||||
|
|
@ -62,8 +62,8 @@ def _import_via_path(path: Path, namespace: str | None = None) -> str | None:
|
|||
return namespace
|
||||
except Exception as err:
|
||||
error(
|
||||
f'Error: {err}',
|
||||
f"The above error was detected when loading the plugin: {path}"
|
||||
f"Error: {err}",
|
||||
f"The above error was detected when loading the plugin: {path}",
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
@ -91,29 +91,29 @@ def load_plugin(path: Path) -> None:
|
|||
# Path was not found in any known examples, check if it's an absolute path
|
||||
if os.path.isfile(path):
|
||||
namespace = _import_via_path(path)
|
||||
elif parsed_url.scheme in ('https', 'http'):
|
||||
elif parsed_url.scheme in ("https", "http"):
|
||||
localized = _localize_path(path)
|
||||
namespace = _import_via_path(localized)
|
||||
|
||||
if namespace and namespace in sys.modules:
|
||||
# Version dependency via __archinstall__version__ variable (if present) in the plugin
|
||||
# Any errors in version inconsistency will be handled through normal error handling if not defined.
|
||||
if hasattr(sys.modules[namespace], '__archinstall__version__'):
|
||||
archinstall_major_and_minor_version = float(storage['__version__'][:_find_nth(storage['__version__'], '.', 2)])
|
||||
if hasattr(sys.modules[namespace], "__archinstall__version__"):
|
||||
archinstall_major_and_minor_version = float(storage["__version__"][: _find_nth(storage["__version__"], ".", 2)])
|
||||
|
||||
if sys.modules[namespace].__archinstall__version__ < archinstall_major_and_minor_version:
|
||||
error(f"Plugin {sys.modules[namespace]} does not support the current Archinstall version.")
|
||||
|
||||
# Locate the plugin entry-point called Plugin()
|
||||
# This in accordance with the entry_points() from setup.cfg above
|
||||
if hasattr(sys.modules[namespace], 'Plugin'):
|
||||
if hasattr(sys.modules[namespace], "Plugin"):
|
||||
try:
|
||||
plugins[namespace] = sys.modules[namespace].Plugin()
|
||||
info(f"Plugin {plugins[namespace]} has been loaded.")
|
||||
except Exception as err:
|
||||
error(
|
||||
f'Error: {err}',
|
||||
f"The above error was detected when initiating the plugin: {path}"
|
||||
f"Error: {err}",
|
||||
f"The above error was detected when initiating the plugin: {path}",
|
||||
)
|
||||
else:
|
||||
warn(f"Plugin '{path}' is missing a valid entry-point or is corrupt.")
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
|||
class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
||||
def __init__(
|
||||
self,
|
||||
preset: ProfileConfiguration | None = None
|
||||
preset: ProfileConfiguration | None = None,
|
||||
):
|
||||
if preset:
|
||||
self._profile_config = preset
|
||||
|
|
@ -37,36 +37,36 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
|||
super().__init__(
|
||||
self._item_group,
|
||||
self._profile_config,
|
||||
allow_reset=True
|
||||
allow_reset=True,
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=str(_('Type')),
|
||||
text=str(_("Type")),
|
||||
action=self._select_profile,
|
||||
value=self._profile_config.profile,
|
||||
preview_action=self._preview_profile,
|
||||
key='profile'
|
||||
key="profile",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Graphics driver')),
|
||||
text=str(_("Graphics driver")),
|
||||
action=self._select_gfx_driver,
|
||||
value=self._profile_config.gfx_driver if self._profile_config.profile and self._profile_config.profile.is_graphic_driver_supported() else None,
|
||||
preview_action=self._prev_gfx,
|
||||
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
|
||||
dependencies=['profile'],
|
||||
key='gfx_driver',
|
||||
dependencies=["profile"],
|
||||
key="gfx_driver",
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Greeter')),
|
||||
text=str(_("Greeter")),
|
||||
action=lambda x: select_greeter(preset=x),
|
||||
value=self._profile_config.greeter if self._profile_config.profile and self._profile_config.profile.is_greeter_supported() else None,
|
||||
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
|
||||
preview_action=self._prev_greeter,
|
||||
dependencies=['profile'],
|
||||
key='greeter',
|
||||
)
|
||||
dependencies=["profile"],
|
||||
key="greeter",
|
||||
),
|
||||
]
|
||||
|
||||
@override
|
||||
|
|
@ -79,36 +79,36 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
|||
|
||||
if profile is not None:
|
||||
if not profile.is_graphic_driver_supported():
|
||||
self._item_group.find_by_key('gfx_driver').enabled = False
|
||||
self._item_group.find_by_key('gfx_driver').value = None
|
||||
self._item_group.find_by_key("gfx_driver").enabled = False
|
||||
self._item_group.find_by_key("gfx_driver").value = None
|
||||
else:
|
||||
self._item_group.find_by_key('gfx_driver').enabled = True
|
||||
self._item_group.find_by_key('gfx_driver').value = GfxDriver.AllOpenSource
|
||||
self._item_group.find_by_key("gfx_driver").enabled = True
|
||||
self._item_group.find_by_key("gfx_driver").value = GfxDriver.AllOpenSource
|
||||
|
||||
if not profile.is_greeter_supported():
|
||||
self._item_group.find_by_key('greeter').enabled = False
|
||||
self._item_group.find_by_key('greeter').value = None
|
||||
self._item_group.find_by_key("greeter").enabled = False
|
||||
self._item_group.find_by_key("greeter").value = None
|
||||
else:
|
||||
self._item_group.find_by_key('greeter').enabled = True
|
||||
self._item_group.find_by_key('greeter').value = profile.default_greeter_type
|
||||
self._item_group.find_by_key("greeter").enabled = True
|
||||
self._item_group.find_by_key("greeter").value = profile.default_greeter_type
|
||||
else:
|
||||
self._item_group.find_by_key('gfx_driver').value = None
|
||||
self._item_group.find_by_key('greeter').value = None
|
||||
self._item_group.find_by_key("gfx_driver").value = None
|
||||
self._item_group.find_by_key("greeter").value = None
|
||||
|
||||
return profile
|
||||
|
||||
def _select_gfx_driver(self, preset: GfxDriver | None = None) -> GfxDriver | None:
|
||||
driver = preset
|
||||
profile: Profile | None = self._item_group.find_by_key('profile').value
|
||||
profile: Profile | None = self._item_group.find_by_key("profile").value
|
||||
|
||||
if profile:
|
||||
if profile.is_graphic_driver_supported():
|
||||
driver = select_driver(preset=preset)
|
||||
|
||||
if driver and 'Sway' in profile.current_selection_names():
|
||||
if driver and "Sway" in profile.current_selection_names():
|
||||
if driver.is_nvidia():
|
||||
header = str(_('The proprietary Nvidia driver is not supported by Sway.')) + '\n'
|
||||
header += str(_('It is likely that you will run into issues, are you okay with that?')) + '\n'
|
||||
header = str(_("The proprietary Nvidia driver is not supported by Sway.")) + "\n"
|
||||
header += str(_("It is likely that you will run into issues, are you okay with that?")) + "\n"
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.focus_item = MenuItem.no()
|
||||
|
|
@ -120,7 +120,7 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
|||
allow_skip=False,
|
||||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
alignment=Alignment.CENTER
|
||||
alignment=Alignment.CENTER,
|
||||
).run()
|
||||
|
||||
if result.item() == MenuItem.no():
|
||||
|
|
@ -132,25 +132,25 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
|||
if item.value:
|
||||
driver = item.get_value().value
|
||||
packages = item.get_value().packages_text()
|
||||
return f'Driver: {driver}\n{packages}'
|
||||
return f"Driver: {driver}\n{packages}"
|
||||
return None
|
||||
|
||||
def _prev_greeter(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
return f'{_("Greeter")}: {item.value.value}'
|
||||
return f"{_('Greeter')}: {item.value.value}"
|
||||
return None
|
||||
|
||||
def _preview_profile(self, item: MenuItem) -> str | None:
|
||||
profile: Profile | None = item.value
|
||||
text = ''
|
||||
text = ""
|
||||
|
||||
if profile:
|
||||
if (sub_profiles := profile.current_selection) is not None:
|
||||
text += str(_('Selected profiles: '))
|
||||
text += ', '.join([p.name for p in sub_profiles]) + '\n'
|
||||
text += str(_("Selected profiles: "))
|
||||
text += ", ".join([p.name for p in sub_profiles]) + "\n"
|
||||
|
||||
if packages := profile.packages_text(include_sub_packages=True):
|
||||
text += f'{packages}'
|
||||
text += f"{packages}"
|
||||
|
||||
if text:
|
||||
return text
|
||||
|
|
@ -160,7 +160,7 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
|||
|
||||
def select_greeter(
|
||||
profile: Profile | None = None,
|
||||
preset: GreeterType | None = None
|
||||
preset: GreeterType | None = None,
|
||||
) -> GreeterType | None:
|
||||
if not profile or profile.is_greeter_supported():
|
||||
items = [MenuItem(greeter.value, value=greeter) for greeter in GreeterType]
|
||||
|
|
@ -178,8 +178,8 @@ def select_greeter(
|
|||
result = SelectMenu[GreeterType](
|
||||
group,
|
||||
allow_skip=True,
|
||||
frame=FrameProperties.min(str(_('Greeter'))),
|
||||
alignment=Alignment.CENTER
|
||||
frame=FrameProperties.min(str(_("Greeter"))),
|
||||
alignment=Alignment.CENTER,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
@ -188,7 +188,7 @@ def select_greeter(
|
|||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
raise ValueError("Unhandled result type")
|
||||
|
||||
return None
|
||||
|
||||
|
|
@ -199,10 +199,11 @@ def select_profile(
|
|||
allow_reset: bool = True,
|
||||
) -> Profile | None:
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
|
||||
top_level_profiles = profile_handler.get_top_level_profiles()
|
||||
|
||||
if header is None:
|
||||
header = str(_('This is a list of pre-programmed default_profiles')) + '\n'
|
||||
header = str(_("This is a list of pre-programmed default_profiles")) + "\n"
|
||||
|
||||
items = [MenuItem(p.name, value=p) for p in top_level_profiles]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
|
@ -214,7 +215,7 @@ def select_profile(
|
|||
allow_reset=allow_reset,
|
||||
allow_skip=True,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(str(_('Main profile')))
|
||||
frame=FrameProperties.min(str(_("Main profile"))),
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
|
|
|
|||
|
|
@ -50,13 +50,13 @@ class ProfileHandler:
|
|||
|
||||
if profile is not None:
|
||||
data = {
|
||||
'main': profile.name,
|
||||
'details': [profile.name for profile in profile.current_selection],
|
||||
'custom_settings': {profile.name: profile.custom_settings for profile in profile.current_selection}
|
||||
"main": profile.name,
|
||||
"details": [profile.name for profile in profile.current_selection],
|
||||
"custom_settings": {profile.name: profile.custom_settings for profile in profile.current_selection},
|
||||
}
|
||||
|
||||
if self._url_path is not None:
|
||||
data['path'] = self._url_path
|
||||
data["path"] = self._url_path
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ class ProfileHandler:
|
|||
# load all the default_profiles from url and custom
|
||||
# so that we can then apply whatever was specified
|
||||
# in the main/detail sections
|
||||
if url_path := profile_config.get('path', None):
|
||||
if url_path := profile_config.get("path", None):
|
||||
self._url_path = url_path
|
||||
local_path = Path(url_path)
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ class ProfileHandler:
|
|||
# if custom_profile := self.get_profile_by_name('Custom'):
|
||||
# custom_profile.set_current_selection(custom_types)
|
||||
|
||||
if main := profile_config.get('main', None):
|
||||
if main := profile_config.get("main", None):
|
||||
profile = self.get_profile_by_name(main) if main else None
|
||||
|
||||
if not profile:
|
||||
|
|
@ -112,14 +112,14 @@ class ProfileHandler:
|
|||
|
||||
valid_sub_profiles: list[Profile] = []
|
||||
invalid_sub_profiles: list[str] = []
|
||||
details: list[str] = profile_config.get('details', [])
|
||||
details: list[str] = profile_config.get("details", [])
|
||||
|
||||
if details:
|
||||
for detail in filter(None, details):
|
||||
# [2024-04-19] TODO: Backwards compatibility after naming change: https://github.com/archlinux/archinstall/pull/2421
|
||||
# 'Kde' is deprecated, remove this block in a future version
|
||||
if detail == 'Kde':
|
||||
detail = 'KDE Plasma'
|
||||
if detail == "Kde":
|
||||
detail = "KDE Plasma"
|
||||
|
||||
if sub_profile := self.get_profile_by_name(detail):
|
||||
valid_sub_profiles.append(sub_profile)
|
||||
|
|
@ -127,9 +127,9 @@ class ProfileHandler:
|
|||
invalid_sub_profiles.append(detail)
|
||||
|
||||
if invalid_sub_profiles:
|
||||
info('No profile definition found: {}'.format(', '.join(invalid_sub_profiles)))
|
||||
info("No profile definition found: {}".format(", ".join(invalid_sub_profiles)))
|
||||
|
||||
custom_settings = profile_config.get('custom_settings', {})
|
||||
custom_settings = profile_config.get("custom_settings", {})
|
||||
profile.current_selection = valid_sub_profiles
|
||||
|
||||
for sub_profile in valid_sub_profiles:
|
||||
|
|
@ -186,28 +186,28 @@ class ProfileHandler:
|
|||
tailored = [p for p in self.profiles if p.is_tailored()]
|
||||
return [t for t in tailored if t.name in self._local_mac_addresses]
|
||||
|
||||
def install_greeter(self, install_session: 'Installer', greeter: GreeterType) -> None:
|
||||
def install_greeter(self, install_session: "Installer", greeter: GreeterType) -> None:
|
||||
packages = []
|
||||
service = None
|
||||
|
||||
match greeter:
|
||||
case GreeterType.LightdmSlick:
|
||||
packages = ['lightdm', 'lightdm-slick-greeter']
|
||||
service = ['lightdm']
|
||||
packages = ["lightdm", "lightdm-slick-greeter"]
|
||||
service = ["lightdm"]
|
||||
case GreeterType.Lightdm:
|
||||
packages = ['lightdm', 'lightdm-gtk-greeter']
|
||||
service = ['lightdm']
|
||||
packages = ["lightdm", "lightdm-gtk-greeter"]
|
||||
service = ["lightdm"]
|
||||
case GreeterType.Sddm:
|
||||
packages = ['sddm']
|
||||
service = ['sddm']
|
||||
packages = ["sddm"]
|
||||
service = ["sddm"]
|
||||
case GreeterType.Gdm:
|
||||
packages = ['gdm']
|
||||
service = ['gdm']
|
||||
packages = ["gdm"]
|
||||
service = ["gdm"]
|
||||
case GreeterType.Ly:
|
||||
packages = ['ly']
|
||||
service = ['ly']
|
||||
packages = ["ly"]
|
||||
service = ["ly"]
|
||||
case GreeterType.CosmicSession:
|
||||
packages = ['cosmic-greeter']
|
||||
packages = ["cosmic-greeter"]
|
||||
|
||||
if packages:
|
||||
install_session.add_additional_packages(packages)
|
||||
|
|
@ -216,35 +216,35 @@ class ProfileHandler:
|
|||
|
||||
# slick-greeter requires a config change
|
||||
if greeter == GreeterType.LightdmSlick:
|
||||
path = install_session.target.joinpath('etc/lightdm/lightdm.conf')
|
||||
path = install_session.target.joinpath("etc/lightdm/lightdm.conf")
|
||||
with open(path) as file:
|
||||
filedata = file.read()
|
||||
|
||||
filedata = filedata.replace('#greeter-session=example-gtk-gnome', 'greeter-session=lightdm-slick-greeter')
|
||||
filedata = filedata.replace("#greeter-session=example-gtk-gnome", "greeter-session=lightdm-slick-greeter")
|
||||
|
||||
with open(path, 'w') as file:
|
||||
with open(path, "w") as file:
|
||||
file.write(filedata)
|
||||
|
||||
def install_gfx_driver(self, install_session: 'Installer', driver: GfxDriver) -> None:
|
||||
debug(f'Installing GFX driver: {driver.value}')
|
||||
def install_gfx_driver(self, install_session: "Installer", driver: GfxDriver) -> None:
|
||||
debug(f"Installing GFX driver: {driver.value}")
|
||||
|
||||
if driver in [GfxDriver.NvidiaOpenKernel, GfxDriver.NvidiaProprietary]:
|
||||
headers = [f'{kernel}-headers' for kernel in install_session.kernels]
|
||||
headers = [f"{kernel}-headers" for kernel in install_session.kernels]
|
||||
# Fixes https://github.com/archlinux/archinstall/issues/585
|
||||
install_session.add_additional_packages(headers)
|
||||
elif driver in [GfxDriver.AllOpenSource, GfxDriver.AmdOpenSource]:
|
||||
# The order of these two are important if amdgpu is installed #808
|
||||
install_session.remove_mod('amdgpu')
|
||||
install_session.remove_mod('radeon')
|
||||
install_session.remove_mod("amdgpu")
|
||||
install_session.remove_mod("radeon")
|
||||
|
||||
install_session.append_mod('amdgpu')
|
||||
install_session.append_mod('radeon')
|
||||
install_session.append_mod("amdgpu")
|
||||
install_session.append_mod("radeon")
|
||||
|
||||
driver_pkgs = driver.gfx_packages()
|
||||
pkg_names = [p.value for p in driver_pkgs]
|
||||
install_session.add_additional_packages(pkg_names)
|
||||
|
||||
def install_profile_config(self, install_session: 'Installer', profile_config: ProfileConfiguration) -> None:
|
||||
def install_profile_config(self, install_session: "Installer", profile_config: ProfileConfiguration) -> None:
|
||||
profile = profile_config.profile
|
||||
|
||||
if not profile:
|
||||
|
|
@ -264,9 +264,9 @@ class ProfileHandler:
|
|||
"""
|
||||
try:
|
||||
data = fetch_data_from_url(url)
|
||||
b_data = bytes(data, 'utf-8')
|
||||
b_data = bytes(data, "utf-8")
|
||||
|
||||
with NamedTemporaryFile(delete=False, suffix='.py') as fp:
|
||||
with NamedTemporaryFile(delete=False, suffix=".py") as fp:
|
||||
fp.write(b_data)
|
||||
filepath = Path(fp.name)
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ class ProfileHandler:
|
|||
self.remove_custom_profiles(profiles)
|
||||
self.add_custom_profiles(profiles)
|
||||
except ValueError:
|
||||
err = str(_('Unable to fetch profile from specified url: {}')).format(url)
|
||||
err = str(_("Unable to fetch profile from specified url: {}")).format(url)
|
||||
error(err)
|
||||
|
||||
def _load_profile_class(self, module: ModuleType) -> list[Profile]:
|
||||
|
|
@ -292,7 +292,7 @@ class ProfileHandler:
|
|||
if isinstance(cls_, Profile):
|
||||
profiles.append(cls_)
|
||||
except Exception:
|
||||
debug(f'Cannot import {module}, it does not appear to be a Profile class')
|
||||
debug(f"Cannot import {module}, it does not appear to be a Profile class")
|
||||
|
||||
return profiles
|
||||
|
||||
|
|
@ -305,7 +305,7 @@ class ProfileHandler:
|
|||
duplicates = [x for x in counter.items() if x[1] != 1]
|
||||
|
||||
if len(duplicates) > 0:
|
||||
err = str(_('Profiles must have unique name, but profile definitions with duplicate name found: {}')).format(duplicates[0][0])
|
||||
err = str(_("Profiles must have unique name, but profile definitions with duplicate name found: {}")).format(duplicates[0][0])
|
||||
error(err)
|
||||
sys.exit(1)
|
||||
|
||||
|
|
@ -316,7 +316,7 @@ class ProfileHandler:
|
|||
"""
|
||||
with open(file) as fp:
|
||||
for line in fp.readlines():
|
||||
if '__packages__' in line:
|
||||
if "__packages__" in line:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -325,15 +325,15 @@ class ProfileHandler:
|
|||
Process a file for profile definitions
|
||||
"""
|
||||
if self._is_legacy(file):
|
||||
info(f'Cannot import {file} because it is no longer supported, please use the new profile format')
|
||||
info(f"Cannot import {file} because it is no longer supported, please use the new profile format")
|
||||
return []
|
||||
|
||||
if not file.is_file():
|
||||
info(f'Cannot find profile file {file}')
|
||||
info(f"Cannot find profile file {file}")
|
||||
return []
|
||||
|
||||
name = file.name.removesuffix(file.suffix)
|
||||
debug(f'Importing profile: {file}')
|
||||
debug(f"Importing profile: {file}")
|
||||
|
||||
try:
|
||||
if spec := importlib.util.spec_from_file_location(name, file):
|
||||
|
|
@ -342,7 +342,7 @@ class ProfileHandler:
|
|||
spec.loader.exec_module(imported)
|
||||
return self._load_profile_class(imported)
|
||||
except Exception as e:
|
||||
error(f'Unable to parse file {file}: {e}')
|
||||
error(f"Unable to parse file {file}: {e}")
|
||||
|
||||
return []
|
||||
|
||||
|
|
@ -350,11 +350,11 @@ class ProfileHandler:
|
|||
"""
|
||||
Search the profile path for profile definitions
|
||||
"""
|
||||
profiles_path = Path(__file__).parents[2] / 'default_profiles'
|
||||
profiles_path = Path(__file__).parents[2] / "default_profiles"
|
||||
profiles = []
|
||||
for file in profiles_path.glob('**/*.py'):
|
||||
for file in profiles_path.glob("**/*.py"):
|
||||
# ignore the abstract default_profiles class
|
||||
if 'profile.py' in file.name:
|
||||
if "profile.py" in file.name:
|
||||
continue
|
||||
profiles += self._process_profile_file(file)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ from pathlib import Path
|
|||
from typing import Any
|
||||
|
||||
storage: dict[str, Any] = {
|
||||
'LOG_PATH': Path('/var/log/archinstall'),
|
||||
'LOG_FILE': Path('install.log'),
|
||||
"LOG_PATH": Path("/var/log/archinstall"),
|
||||
"LOG_FILE": Path("install.log"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class Language:
|
|||
@property
|
||||
def display_name(self) -> str:
|
||||
name = self.name_en
|
||||
return f'{name} ({self.translation_percent}%)'
|
||||
return f"{name} ({self.translation_percent}%)"
|
||||
|
||||
def is_match(self, lang_or_translated_lang: str) -> bool:
|
||||
if self.name_en == lang_or_translated_lang:
|
||||
|
|
@ -38,8 +38,8 @@ class Language:
|
|||
|
||||
class TranslationHandler:
|
||||
def __init__(self) -> None:
|
||||
self._base_pot = 'base.pot'
|
||||
self._languages = 'languages.json'
|
||||
self._base_pot = "base.pot"
|
||||
self._languages = "languages.json"
|
||||
|
||||
self._total_messages = self._get_total_active_messages()
|
||||
self._translated_languages = self._get_translations()
|
||||
|
|
@ -58,17 +58,17 @@ class TranslationHandler:
|
|||
languages = []
|
||||
|
||||
for short_form in defined_languages:
|
||||
mapping_entry: dict[str, str] = next(filter(lambda x: x['abbr'] == short_form, mappings))
|
||||
abbr = mapping_entry['abbr']
|
||||
lang = mapping_entry['lang']
|
||||
translated_lang = mapping_entry.get('translated_lang', None)
|
||||
mapping_entry: dict[str, str] = next(filter(lambda x: x["abbr"] == short_form, mappings))
|
||||
abbr = mapping_entry["abbr"]
|
||||
lang = mapping_entry["lang"]
|
||||
translated_lang = mapping_entry.get("translated_lang", None)
|
||||
|
||||
try:
|
||||
# get a translation for a specific language
|
||||
translation = gettext.translation('base', localedir=self._get_locales_dir(), languages=(abbr, lang))
|
||||
translation = gettext.translation("base", localedir=self._get_locales_dir(), languages=(abbr, lang))
|
||||
|
||||
# calculate the percentage of total translated text to total number of messages
|
||||
if abbr == 'en':
|
||||
if abbr == "en":
|
||||
percent = 100
|
||||
else:
|
||||
num_translations = self._get_catalog_size(translation)
|
||||
|
|
@ -108,9 +108,9 @@ class TranslationHandler:
|
|||
Get total messages that could be translated
|
||||
"""
|
||||
locales = self._get_locales_dir()
|
||||
with open(f'{locales}/{self._base_pot}') as fp:
|
||||
with open(f"{locales}/{self._base_pot}") as fp:
|
||||
lines = fp.readlines()
|
||||
msgid_lines = [line for line in lines if 'msgid' in line]
|
||||
msgid_lines = [line for line in lines if "msgid" in line]
|
||||
|
||||
return len(msgid_lines) - 1 # don't count the first line which contains the metadata
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ class TranslationHandler:
|
|||
try:
|
||||
return next(filter(lambda x: x.name_en == name, self._translated_languages))
|
||||
except Exception:
|
||||
raise ValueError(f'No language with name found: {name}')
|
||||
raise ValueError(f"No language with name found: {name}")
|
||||
|
||||
def get_language_by_abbr(self, abbr: str) -> Language:
|
||||
"""
|
||||
|
|
@ -143,7 +143,7 @@ class TranslationHandler:
|
|||
Get the locales directory path
|
||||
"""
|
||||
cur_path = Path(__file__).parent.parent
|
||||
locales_dir = Path.joinpath(cur_path, 'locales')
|
||||
locales_dir = Path.joinpath(cur_path, "locales")
|
||||
return locales_dir
|
||||
|
||||
def _provided_translations(self) -> list[str]:
|
||||
|
|
@ -155,7 +155,7 @@ class TranslationHandler:
|
|||
|
||||
translation_files = []
|
||||
for filename in filenames:
|
||||
if len(filename) == 2 or filename in ['pt_BR', 'zh-CN', 'zh-TW']:
|
||||
if len(filename) == 2 or filename in ["pt_BR", "zh-CN", "zh-TW"]:
|
||||
translation_files.append(filename)
|
||||
|
||||
return translation_files
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from functools import lru_cache
|
|||
|
||||
@lru_cache(maxsize=128)
|
||||
def _is_wide_character(char: str) -> bool:
|
||||
return unicodedata.east_asian_width(char) in 'FW'
|
||||
return unicodedata.east_asian_width(char) in "FW"
|
||||
|
||||
|
||||
def _count_wchars(string: str) -> int:
|
||||
|
|
@ -12,7 +12,7 @@ def _count_wchars(string: str) -> int:
|
|||
return sum(_is_wide_character(c) for c in string)
|
||||
|
||||
|
||||
def unicode_ljust(string: str, width: int, fillbyte: str = ' ') -> str:
|
||||
def unicode_ljust(string: str, width: int, fillbyte: str = " ") -> str:
|
||||
"""Return a left-justified unicode string of length width.
|
||||
>>> unicode_ljust('Hello', 15, '*')
|
||||
'Hello**********'
|
||||
|
|
@ -26,7 +26,7 @@ def unicode_ljust(string: str, width: int, fillbyte: str = ' ') -> str:
|
|||
return string.ljust(width - _count_wchars(string), fillbyte)
|
||||
|
||||
|
||||
def unicode_rjust(string: str, width: int, fillbyte: str = ' ') -> str:
|
||||
def unicode_rjust(string: str, width: int, fillbyte: str = " ") -> str:
|
||||
"""Return a right-justified unicode string of length width.
|
||||
>>> unicode_rjust('Hello', 15, '*')
|
||||
'**********Hello'
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ def get_password(
|
|||
header: str | None = None,
|
||||
allow_skip: bool = False,
|
||||
preset: str | None = None,
|
||||
skip_confirmation: bool = False
|
||||
skip_confirmation: bool = False,
|
||||
) -> Password | None:
|
||||
failure: str | None = None
|
||||
|
||||
while True:
|
||||
user_hdr = None
|
||||
if failure is not None:
|
||||
user_hdr = f'{header}\n{failure}\n'
|
||||
user_hdr = f"{header}\n{failure}\n"
|
||||
elif header is not None:
|
||||
user_hdr = header
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ def get_password(
|
|||
alignment=Alignment.CENTER,
|
||||
allow_skip=allow_skip,
|
||||
default_text=preset,
|
||||
hide_input=True
|
||||
hide_input=True,
|
||||
).input()
|
||||
|
||||
if allow_skip and not result.has_item():
|
||||
|
|
@ -49,22 +49,22 @@ def get_password(
|
|||
return password
|
||||
|
||||
if header is not None:
|
||||
confirmation_header = f'{header}{_("Password")}: {password.hidden()}\n'
|
||||
confirmation_header = f"{header}{_('Password')}: {password.hidden()}\n"
|
||||
else:
|
||||
confirmation_header = f'{_("Password")}: {password.hidden()}\n'
|
||||
confirmation_header = f"{_('Password')}: {password.hidden()}\n"
|
||||
|
||||
result = EditMenu(
|
||||
str(_('Confirm password')),
|
||||
str(_("Confirm password")),
|
||||
header=confirmation_header,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=False,
|
||||
hide_input=True
|
||||
hide_input=True,
|
||||
).input()
|
||||
|
||||
if password._plaintext == result.text():
|
||||
return password
|
||||
|
||||
failure = str(_('The confirmation password did not match, please try again'))
|
||||
failure = str(_("The confirmation password did not match, please try again"))
|
||||
|
||||
|
||||
def prompt_dir(
|
||||
|
|
@ -72,7 +72,7 @@ def prompt_dir(
|
|||
header: str | None = None,
|
||||
validate: bool = True,
|
||||
allow_skip: bool = False,
|
||||
preset: str | None = None
|
||||
preset: str | None = None,
|
||||
) -> Path | None:
|
||||
def validate_path(path: str) -> str | None:
|
||||
dest_path = Path(path)
|
||||
|
|
@ -80,7 +80,7 @@ def prompt_dir(
|
|||
if dest_path.exists() and dest_path.is_dir():
|
||||
return None
|
||||
|
||||
return str(_('Not a valid directory'))
|
||||
return str(_("Not a valid directory"))
|
||||
|
||||
if validate:
|
||||
validate_func = validate_path
|
||||
|
|
@ -93,7 +93,7 @@ def prompt_dir(
|
|||
alignment=Alignment.CENTER,
|
||||
allow_skip=allow_skip,
|
||||
validator=validate_func,
|
||||
default_text=preset
|
||||
default_text=preset,
|
||||
).input()
|
||||
|
||||
if allow_skip and not result.has_item():
|
||||
|
|
@ -115,9 +115,9 @@ def is_subpath(first: Path, second: Path) -> bool:
|
|||
|
||||
def format_cols(items: list[str], header: str | None = None) -> str:
|
||||
if header:
|
||||
text = f'{header}:\n'
|
||||
text = f"{header}:\n"
|
||||
else:
|
||||
text = ''
|
||||
text = ""
|
||||
|
||||
nr_items = len(items)
|
||||
if nr_items <= 4:
|
||||
|
|
@ -131,5 +131,5 @@ def format_cols(items: list[str], header: str | None = None) -> str:
|
|||
|
||||
text += FormattedOutput.as_columns(items, col)
|
||||
# remove whitespaces on each row
|
||||
text = '\n'.join([t.strip() for t in text.split('\n')])
|
||||
text = "\n".join([t.strip() for t in text.split("\n")])
|
||||
return text
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ def ask_user_questions() -> None:
|
|||
global_menu = GlobalMenu(arch_config_handler.config)
|
||||
|
||||
if not arch_config_handler.args.advanced:
|
||||
global_menu.set_enabled('parallel_downloads', False)
|
||||
global_menu.set_enabled("parallel_downloads", False)
|
||||
|
||||
global_menu.run()
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
Only requirement is that the block devices are
|
||||
formatted and setup prior to entering this function.
|
||||
"""
|
||||
info('Starting installation...')
|
||||
info("Starting installation...")
|
||||
|
||||
config = arch_config_handler.config
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
mountpoint,
|
||||
disk_config,
|
||||
disk_encryption=disk_encryption,
|
||||
kernels=config.kernels
|
||||
kernels=config.kernels,
|
||||
) as installation:
|
||||
# Mount all the drives to the desired mountpoint
|
||||
if disk_config.config_type != DiskLayoutType.Pre_mount:
|
||||
|
|
@ -80,14 +80,14 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
optional_repositories=optional_repositories,
|
||||
mkinitcpio=run_mkinitcpio,
|
||||
hostname=arch_config_handler.config.hostname,
|
||||
locale_config=locale_config
|
||||
locale_config=locale_config,
|
||||
)
|
||||
|
||||
if mirror_config := config.mirror_config:
|
||||
installation.set_mirrors(mirror_config, on_target=True)
|
||||
|
||||
if config.swap:
|
||||
installation.setup_swap('zram')
|
||||
installation.setup_swap("zram")
|
||||
|
||||
if config.bootloader == Bootloader.Grub and SysInfo.has_uefi():
|
||||
installation.add_additional_packages("grub")
|
||||
|
|
@ -101,7 +101,7 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
if network_config:
|
||||
network_config.install_network_config(
|
||||
installation,
|
||||
config.profile_config
|
||||
config.profile_config,
|
||||
)
|
||||
|
||||
if users := config.users:
|
||||
|
|
@ -113,7 +113,7 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
else:
|
||||
info("No audio server will be installed")
|
||||
|
||||
if config.packages and config.packages[0] != '':
|
||||
if config.packages and config.packages[0] != "":
|
||||
installation.add_additional_packages(config.packages)
|
||||
|
||||
if profile_config := config.profile_config:
|
||||
|
|
@ -129,7 +129,7 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
installation.enable_espeakup()
|
||||
|
||||
if root_pw := config.root_enc_password:
|
||||
root_user = User('root', root_pw, False)
|
||||
root_user = User("root", root_pw, False)
|
||||
installation.set_user_password(root_user)
|
||||
|
||||
if (profile_config := config.profile_config) and profile_config.profile:
|
||||
|
|
@ -156,7 +156,7 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
case PostInstallationAction.EXIT:
|
||||
pass
|
||||
case PostInstallationAction.REBOOT:
|
||||
os.system('reboot')
|
||||
os.system("reboot")
|
||||
case PostInstallationAction.CHROOT:
|
||||
try:
|
||||
installation.drop_to_shell()
|
||||
|
|
@ -178,13 +178,13 @@ def guided() -> None:
|
|||
if not arch_config_handler.args.silent:
|
||||
with Tui():
|
||||
if not config.confirm_config():
|
||||
debug('Installation aborted')
|
||||
debug("Installation aborted")
|
||||
guided()
|
||||
|
||||
if arch_config_handler.config.disk_config:
|
||||
fs_handler = FilesystemHandler(
|
||||
arch_config_handler.config.disk_config,
|
||||
arch_config_handler.config.disk_encryption
|
||||
arch_config_handler.config.disk_encryption,
|
||||
)
|
||||
|
||||
fs_handler.perform_filesystem_operations()
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue