Integrate new arguments data structure (#3167)
* Integrate new args dataclass * Integrate args * Update * Update * Update * Update
This commit is contained in:
parent
a9ae064359
commit
b57f7f91cf
|
|
@ -1,4 +1,5 @@
|
|||
"""Arch Linux installer - guided, templates etc."""
|
||||
|
||||
import curses
|
||||
import importlib
|
||||
import os
|
||||
|
|
@ -9,29 +10,13 @@ from argparse import ArgumentParser, Namespace
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from . import default_profiles
|
||||
from .lib import disk, exceptions, interactions, locale, luks, mirrors, models, networking, packages, profile
|
||||
from .lib.boot import Boot
|
||||
from .lib.configuration import ConfigurationOutput
|
||||
from .lib.general import (
|
||||
JSON,
|
||||
UNSAFE_JSON,
|
||||
SysCommand,
|
||||
SysCommandWorker,
|
||||
clear_vt100_escape_codes,
|
||||
generate_password,
|
||||
json_stream_to_structure,
|
||||
locate_binary,
|
||||
run_custom_user_commands,
|
||||
secret,
|
||||
)
|
||||
from .lib.global_menu import GlobalMenu
|
||||
from .lib.hardware import GfxDriver, SysInfo
|
||||
from .lib.installer import Installer, accessibility_tools_in_use
|
||||
from archinstall.lib.args import arch_config_handler
|
||||
from archinstall.lib.disk.utils import disk_layouts
|
||||
|
||||
from .lib.hardware import SysInfo
|
||||
from .lib.output import FormattedOutput, debug, error, info, log, warn
|
||||
from .lib.pacman import Pacman
|
||||
from .lib.plugins import load_plugin, plugins
|
||||
from .lib.storage import storage
|
||||
from .lib.translationhandler import DeferredTranslation, Language, translation_handler
|
||||
from .tui import Tui
|
||||
|
||||
|
|
@ -41,9 +26,6 @@ if TYPE_CHECKING:
|
|||
_: Callable[[str], DeferredTranslation]
|
||||
|
||||
|
||||
__version__ = "3.0.2"
|
||||
storage['__version__'] = __version__
|
||||
|
||||
# add the custom _ as a builtin, it can now be used anywhere in the
|
||||
# project to mark strings as translatable with _('translate me')
|
||||
DeferredTranslation.install()
|
||||
|
|
@ -56,236 +38,18 @@ debug(f"Virtualization detected: {SysInfo.virtualization()}; is VM: {SysInfo.is_
|
|||
debug(f"Graphics devices detected: {SysInfo._graphics_devices().keys()}")
|
||||
|
||||
# For support reasons, we'll log the disk layout pre installation to match against post-installation layout
|
||||
debug(f"Disk states before installing:\n{disk.disk_layouts()}")
|
||||
|
||||
parser = ArgumentParser()
|
||||
|
||||
|
||||
def define_arguments() -> None:
|
||||
"""
|
||||
Define which explicit arguments do we allow.
|
||||
Refer to https://docs.python.org/3/library/argparse.html for documentation and
|
||||
https://docs.python.org/3/howto/argparse.html for a tutorial
|
||||
Remember that the property/entry name python assigns to the parameters is the first string defined as argument and
|
||||
dashes inside it '-' are changed to '_'
|
||||
"""
|
||||
parser.add_argument("-v", "--version", action="version", version="%(prog)s " + __version__)
|
||||
parser.add_argument("--config", nargs="?", help="JSON configuration file or URL")
|
||||
parser.add_argument("--creds", nargs="?", help="JSON credentials configuration file")
|
||||
parser.add_argument("--silent", action="store_true",
|
||||
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",
|
||||
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)
|
||||
parser.add_argument("--mount-point", "--mount_point", default=Path("/mnt/archinstall"), nargs="?", type=Path,
|
||||
help="Define an alternate mount point for installation")
|
||||
parser.add_argument("--skip-ntp", action="store_true", help="Disables NTP checks during installation", default=False)
|
||||
parser.add_argument("--debug", action="store_true", default=False, 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.")
|
||||
parser.add_argument("--no-pkg-lookups", action="store_true", default=False,
|
||||
help="Disabled package validation specifically prior to starting installation.")
|
||||
parser.add_argument("--plugin", nargs="?", type=str)
|
||||
parser.add_argument("--skip-version-check", action="store_true",
|
||||
help="Skip the version check when running archinstall")
|
||||
debug(f"Disk states before installing:\n{disk_layouts()}")
|
||||
|
||||
|
||||
if 'sphinx' not in sys.modules and 'pylint' not in sys.modules:
|
||||
if '--help' in sys.argv or '-h' in sys.argv:
|
||||
define_arguments()
|
||||
parser.print_help()
|
||||
arch_config_handler.print_help()
|
||||
exit(0)
|
||||
if os.getuid() != 0:
|
||||
print(_("Archinstall requires root privileges to run. See --help for more."))
|
||||
exit(1)
|
||||
|
||||
|
||||
def parse_unspecified_argument_list(unknowns: list, multiple: bool = False, err: bool = False) -> dict: # type: ignore[type-arg]
|
||||
"""We accept arguments not defined to the parser. (arguments "ad hoc").
|
||||
Internally argparse return to us a list of words so we have to parse its contents, manually.
|
||||
We accept following individual syntax for each argument
|
||||
--argument value
|
||||
--argument=value
|
||||
--argument = value
|
||||
--argument (boolean as default)
|
||||
the optional parameters to the function alter a bit its behaviour:
|
||||
* multiple allows multivalued arguments, each value separated by whitespace. They're returned as a list
|
||||
* error. If set any non correctly specified argument-value pair to raise an exception. Else, simply notifies the
|
||||
existence of a problem and continues processing.
|
||||
|
||||
To a certain extent, multiple and error are incompatible. In fact, the only error this routine can catch, as of now,
|
||||
is the event argument value value ...
|
||||
which isn't am error if multiple is specified
|
||||
"""
|
||||
tmp_list = [arg for arg in unknowns if arg != "="] # wastes a few bytes, but avoids any collateral effect of the destructive nature of the pop method()
|
||||
config = {}
|
||||
key = None
|
||||
last_key = None
|
||||
while tmp_list:
|
||||
element = tmp_list.pop(0) # retrieve an element of the list
|
||||
|
||||
if element.startswith('--'): # is an argument ?
|
||||
if '=' in element: # uses the arg=value syntax ?
|
||||
key, value = [x.strip() for x in element[2:].split('=', 1)]
|
||||
config[key] = value
|
||||
last_key = key # for multiple handling
|
||||
key = None # we have the kwy value pair we need
|
||||
else:
|
||||
key = element[2:]
|
||||
config[key] = True # every argument starts its lifecycle as boolean
|
||||
elif key:
|
||||
config[key] = element
|
||||
last_key = key # multiple
|
||||
key = None
|
||||
elif multiple and last_key:
|
||||
if isinstance(config[last_key], str):
|
||||
config[last_key] = [config[last_key], element]
|
||||
else:
|
||||
config[last_key].append(element)
|
||||
elif err:
|
||||
raise ValueError(f"Entry {element} is not related to any argument")
|
||||
else:
|
||||
print(f" We ignore the entry {element} as it isn't related to any argument")
|
||||
return config
|
||||
|
||||
|
||||
def cleanup_empty_args(args: Namespace | dict) -> dict: # type: ignore[type-arg]
|
||||
"""
|
||||
Takes arguments (dictionary or argparse Namespace) and removes any
|
||||
None values. This ensures clean mergers during dict.update(args)
|
||||
"""
|
||||
if type(args) is Namespace:
|
||||
args = vars(args)
|
||||
|
||||
clean_args = {}
|
||||
for key, val in args.items():
|
||||
if isinstance(val, dict):
|
||||
val = cleanup_empty_args(val)
|
||||
|
||||
if val is not None:
|
||||
clean_args[key] = val
|
||||
|
||||
return clean_args
|
||||
|
||||
|
||||
def get_arguments() -> dict[str, Any]:
|
||||
""" The handling of parameters from the command line
|
||||
Is done on following steps:
|
||||
0) we create a dict to store the arguments and their values
|
||||
1) preprocess.
|
||||
We take those arguments which use JSON files, and read them into the argument dict. So each first level entry
|
||||
becomes an argument on its own right
|
||||
2) Load.
|
||||
We convert the predefined argument list directly into the dict via the vars() function. Non specified arguments
|
||||
are loaded with value None or false if they are booleans (action="store_true"). The name is chosen according to
|
||||
argparse conventions. See above (the first text is used as argument name, but underscore substitutes dash). We
|
||||
then load all the undefined arguments. In this case the names are taken as written.
|
||||
Important. This way explicit command line arguments take precedence over configuration files.
|
||||
3) Amend
|
||||
Change whatever is needed on the configuration dictionary (it could be done in post_process_arguments but this
|
||||
ougth to be left to changes anywhere else in the code, not in the arguments dictionary
|
||||
"""
|
||||
config: dict[str, Any] = {}
|
||||
args, unknowns = parser.parse_known_args()
|
||||
# preprocess the JSON files.
|
||||
# TODO Expand the url access to the other JSON file arguments ?
|
||||
if args.config is not None:
|
||||
if not json_stream_to_structure('--config', args.config, config):
|
||||
exit(1)
|
||||
|
||||
if args.creds is not None:
|
||||
if not json_stream_to_structure('--creds', args.creds, config):
|
||||
exit(1)
|
||||
|
||||
# load the parameters. first the known, then the unknowns
|
||||
clean_args = cleanup_empty_args(args)
|
||||
config.update(clean_args)
|
||||
config.update(parse_unspecified_argument_list(unknowns))
|
||||
# amend the parameters (check internal consistency)
|
||||
# Installation can't be silent if config is not passed
|
||||
if clean_args.get('config') is None:
|
||||
config["silent"] = False
|
||||
else:
|
||||
config["silent"] = clean_args.get('silent')
|
||||
|
||||
# avoiding a compatibility issue
|
||||
if 'dry-run' in config:
|
||||
del config['dry-run']
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def load_config() -> None:
|
||||
"""
|
||||
refine and set some arguments. Formerly at the scripts
|
||||
"""
|
||||
from .lib.models import NetworkConfiguration
|
||||
|
||||
arguments['locale_config'] = locale.LocaleConfiguration.parse_arg(arguments)
|
||||
|
||||
if (archinstall_lang := arguments.get('archinstall-language', None)) is not None:
|
||||
arguments['archinstall-language'] = translation_handler.get_language_by_name(archinstall_lang)
|
||||
|
||||
if disk_config := arguments.get('disk_config', {}):
|
||||
arguments['disk_config'] = disk.DiskLayoutConfiguration.parse_arg(disk_config)
|
||||
|
||||
if profile_config := arguments.get('profile_config', None):
|
||||
arguments['profile_config'] = profile.ProfileConfiguration.parse_arg(profile_config)
|
||||
|
||||
if mirror_config := arguments.get('mirror_config', None):
|
||||
arguments['mirror_config'] = mirrors.MirrorConfiguration.parse_args(mirror_config)
|
||||
|
||||
if arguments.get('servers', None) is not None:
|
||||
storage['_selected_servers'] = arguments.get('servers', None)
|
||||
|
||||
if (net_config := arguments.get('network_config', None)) is not None:
|
||||
config = NetworkConfiguration.parse_arg(net_config)
|
||||
arguments['network_config'] = config
|
||||
|
||||
if arguments.get('!users', None) is not None or arguments.get('!superusers', None) is not None:
|
||||
users = arguments.get('!users', None)
|
||||
superusers = arguments.get('!superusers', None)
|
||||
arguments['!users'] = models.User.parse_arguments(users, superusers)
|
||||
|
||||
if arguments.get('bootloader', None) is not None:
|
||||
arguments['bootloader'] = models.Bootloader.from_arg(arguments['bootloader'])
|
||||
|
||||
if arguments.get('uki') and not arguments['bootloader'].has_uki_support():
|
||||
arguments['uki'] = False
|
||||
|
||||
if arguments.get('audio_config', None) is not None:
|
||||
arguments['audio_config'] = models.AudioConfiguration.parse_arg(arguments['audio_config'])
|
||||
|
||||
if arguments.get('disk_encryption', None) is not None and disk_config is not None:
|
||||
arguments['disk_encryption'] = disk.DiskEncryption.parse_arg(
|
||||
arguments['disk_config'],
|
||||
arguments['disk_encryption'],
|
||||
arguments.get('encryption_password', '')
|
||||
)
|
||||
|
||||
|
||||
def post_process_arguments(args: dict[str, Any]) -> None:
|
||||
storage['arguments'] = args
|
||||
|
||||
if args.get('debug'):
|
||||
warn(f"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!")
|
||||
|
||||
if args.get('plugin'):
|
||||
path = args['plugin']
|
||||
load_plugin(path)
|
||||
|
||||
try:
|
||||
load_config()
|
||||
except ValueError as err:
|
||||
warn(str(err))
|
||||
exit(1)
|
||||
|
||||
|
||||
define_arguments()
|
||||
arguments: dict[str, Any] = get_arguments()
|
||||
post_process_arguments(arguments)
|
||||
|
||||
|
||||
# @archinstall.plugin decorator hook to programmatically add
|
||||
# plugins in runtime. Useful in profiles_bck and other things.
|
||||
def plugin(f, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||
|
|
@ -321,13 +85,10 @@ def main() -> None:
|
|||
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 not arguments.get('skip_version_check'):
|
||||
if not arch_config_handler.args.skip_version_check:
|
||||
_check_new_version()
|
||||
|
||||
script = arguments.get('script', None)
|
||||
|
||||
if script is None:
|
||||
print('No script to run provided')
|
||||
script = arch_config_handler.args.script
|
||||
|
||||
mod_name = f'archinstall.scripts.{script}'
|
||||
# by loading the module we'll automatically run the script
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import archinstall
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -26,14 +25,12 @@ class PipewireProfile(Profile):
|
|||
]
|
||||
|
||||
def _enable_pipewire_for_all(self, install_session: 'Installer') -> None:
|
||||
users: User | list[User] | None = archinstall.arguments.get('!users', None)
|
||||
from archinstall.lib.args import arch_config_handler
|
||||
users: list[User] | None = arch_config_handler.config.users
|
||||
|
||||
if users is None:
|
||||
return
|
||||
|
||||
if not isinstance(users, list):
|
||||
users = [users]
|
||||
|
||||
for user in users:
|
||||
# Create the full path for enabling the pipewire systemd items
|
||||
service_dir = install_session.target / "home" / user.username / ".config" / "systemd" / "user" / "default.target.wants"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import sys
|
|||
from enum import Enum, auto
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..lib.storage import storage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
|
|
@ -110,7 +108,8 @@ class Profile:
|
|||
Used to control if the Profile() should be visible or not in different contexts.
|
||||
Returns True if --advanced is given on a Profile(advanced=True) instance.
|
||||
"""
|
||||
return self.advanced is False or storage['arguments'].get('advanced', False) is True
|
||||
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:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import archinstall
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
from archinstall.lib.models import User
|
||||
from archinstall.lib.args import arch_config_handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
|
@ -27,9 +26,5 @@ class DockerProfile(Profile):
|
|||
|
||||
@override
|
||||
def post_install(self, install_session: 'Installer') -> None:
|
||||
users: User | list[User] = archinstall.arguments.get('!users', [])
|
||||
if not isinstance(users, list):
|
||||
users = [users]
|
||||
|
||||
for user in users:
|
||||
for user in arch_config_handler.config.users:
|
||||
install_session.arch_chroot(f'usermod -a -G docker {user.username}')
|
||||
|
|
|
|||
|
|
@ -4,21 +4,22 @@ import urllib.error
|
|||
import urllib.parse
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from dataclasses import dataclass, field
|
||||
from importlib.metadata import version
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from pydantic.dataclasses import dataclass as p_dataclass
|
||||
|
||||
from .disk import DiskEncryption, DiskLayoutConfiguration
|
||||
from .locale import LocaleConfiguration
|
||||
from .mirrors import MirrorConfiguration
|
||||
from .models import AudioConfiguration, Bootloader, NetworkConfiguration, User
|
||||
from .output import error, warn
|
||||
from .plugins import load_plugin
|
||||
from .profile import ProfileConfiguration
|
||||
from .storage import storage
|
||||
from .translationhandler import Language, translation_handler
|
||||
from archinstall.lib.mirrors import MirrorConfiguration
|
||||
from archinstall.lib.models import AudioConfiguration, Bootloader, NetworkConfiguration, User
|
||||
from archinstall.lib.models.device_model import DiskEncryption, DiskLayoutConfiguration
|
||||
from archinstall.lib.models.locale import LocaleConfiguration
|
||||
from archinstall.lib.models.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.output import error, warn
|
||||
from archinstall.lib.plugins import load_plugin
|
||||
from archinstall.lib.storage import storage
|
||||
from archinstall.lib.translationhandler import Language, translation_handler
|
||||
|
||||
|
||||
@p_dataclass
|
||||
|
|
@ -30,7 +31,7 @@ class Arguments:
|
|||
silent: bool = False
|
||||
dry_run: bool = False
|
||||
script: str = 'guided'
|
||||
mount_point: Path | None = Path('/mnt')
|
||||
mountpoint: Path = Path('/mnt')
|
||||
skip_ntp: bool = False
|
||||
debug: bool = False
|
||||
offline: bool = False
|
||||
|
|
@ -38,11 +39,12 @@ class Arguments:
|
|||
plugin: str | None = None
|
||||
skip_version_check: bool = False
|
||||
advanced: bool = False
|
||||
verbose: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class ArchConfig:
|
||||
version: str = field(default_factory=lambda: storage['__version__'])
|
||||
version: str = field(default_factory=lambda: version('archinstall'))
|
||||
locale_config: LocaleConfiguration | None = None
|
||||
archinstall_language: Language = field(default_factory=lambda: translation_handler.get_language_by_abbr('en'))
|
||||
disk_config: DiskLayoutConfiguration | None = None
|
||||
|
|
@ -60,10 +62,62 @@ class ArchConfig:
|
|||
swap: bool = True
|
||||
timezone: str = 'UTC'
|
||||
additional_repositories: list[str] = field(default_factory=list)
|
||||
services: list[str] = field(default_factory=list)
|
||||
custom_commands: list[str] = field(default_factory=list)
|
||||
|
||||
# Special fields that should be handle with care due to security implications
|
||||
_users: list[User] = field(default_factory=list)
|
||||
_disk_encryption: DiskEncryption | None = None
|
||||
users: list[User] = field(default_factory=list)
|
||||
disk_encryption: DiskEncryption | None = None
|
||||
root_password: str | None = None
|
||||
|
||||
def unsafe_json(self) -> dict[str, Any]:
|
||||
config = {
|
||||
'!users': [user.json() for user in self.users],
|
||||
'!root-password': self.root_password,
|
||||
}
|
||||
|
||||
if self.disk_encryption:
|
||||
config['encryption_password'] = self.disk_encryption.encryption_password
|
||||
|
||||
return config
|
||||
|
||||
def safe_json(self) -> dict[str, Any]:
|
||||
config = {
|
||||
'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,
|
||||
'additional-repositories': self.additional_repositories,
|
||||
'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()
|
||||
|
||||
if self.disk_config:
|
||||
config['disk_config'] = self.disk_config.json()
|
||||
|
||||
if self.disk_encryption:
|
||||
config['disk_encryption'] = self.disk_encryption.json()
|
||||
|
||||
if self.profile_config:
|
||||
config['profile_config'] = self.profile_config.json()
|
||||
|
||||
if self.mirror_config:
|
||||
config['mirror_config'] = self.mirror_config.json()
|
||||
|
||||
if self.network_config:
|
||||
config['network_config'] = self.network_config.json()
|
||||
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, args_config: dict[str, Any]) -> 'ArchConfig':
|
||||
|
|
@ -89,7 +143,7 @@ class ArchConfig:
|
|||
users = args_config.get('!users', None)
|
||||
superusers = args_config.get('!superusers', None)
|
||||
if users is not None or superusers is not None:
|
||||
arch_config._users = User.parse_arguments(users, superusers)
|
||||
arch_config.users = User.parse_arguments(users, superusers)
|
||||
|
||||
if bootloader_config := args_config.get('bootloader', None):
|
||||
arch_config.bootloader = Bootloader.from_arg(bootloader_config)
|
||||
|
|
@ -101,7 +155,7 @@ class ArchConfig:
|
|||
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:
|
||||
arch_config._disk_encryption = DiskEncryption.parse_arg(
|
||||
arch_config.disk_encryption = DiskEncryption.parse_arg(
|
||||
arch_config.disk_config,
|
||||
args_config['disk_encryption'],
|
||||
args_config.get('encryption_password', '')
|
||||
|
|
@ -130,6 +184,15 @@ class ArchConfig:
|
|||
if additional_repositories := args_config.get('additional-repositories', []):
|
||||
arch_config.additional_repositories = additional_repositories
|
||||
|
||||
if services := args_config.get('services', []):
|
||||
arch_config.services = services
|
||||
|
||||
if root_password := args_config.get('!root-password', None):
|
||||
arch_config.root_password = root_password
|
||||
|
||||
if custom_commands := args_config.get('custom_commands', []):
|
||||
arch_config.custom_commands = custom_commands
|
||||
|
||||
return arch_config
|
||||
|
||||
|
||||
|
|
@ -139,11 +202,11 @@ class ArchConfigHandler:
|
|||
self._args: Arguments = self._parse_args()
|
||||
|
||||
config = self._parse_config()
|
||||
self._arch_config = ArchConfig.from_config(config)
|
||||
self._config = ArchConfig.from_config(config)
|
||||
|
||||
@property
|
||||
def arch_config(self) -> ArchConfig:
|
||||
return self._arch_config
|
||||
def config(self) -> ArchConfig:
|
||||
return self._config
|
||||
|
||||
@property
|
||||
def args(self) -> Arguments:
|
||||
|
|
@ -159,7 +222,7 @@ class ArchConfigHandler:
|
|||
"--version",
|
||||
action="version",
|
||||
default=False,
|
||||
version="%(prog)s " + storage['__version__']
|
||||
version="%(prog)s " + version('archinstall')
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
|
|
@ -260,6 +323,12 @@ class ArchConfigHandler:
|
|||
default=False,
|
||||
help="Enabled advanced options"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Enabled verbose options"
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
|
@ -336,3 +405,6 @@ class ArchConfigHandler:
|
|||
clean_args[key] = val
|
||||
|
||||
return clean_args
|
||||
|
||||
|
||||
arch_config_handler: ArchConfigHandler = ArchConfigHandler()
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import json
|
|||
import readline
|
||||
import stat
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.tui import Alignment, FrameProperties, MenuItem, MenuItemGroup, Orientation, PreviewStyle, ResultType, SelectMenu, Tui
|
||||
|
||||
from .args import ArchConfig
|
||||
from .general import JSON, UNSAFE_JSON
|
||||
from .output import debug, warn
|
||||
from .storage import storage
|
||||
|
|
@ -20,58 +21,36 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class ConfigurationOutput:
|
||||
def __init__(self, config: dict[str, Any]):
|
||||
def __init__(self, config: ArchConfig):
|
||||
"""
|
||||
Configuration output handler to parse the existing configuration data structure and prepare for output on the
|
||||
Configuration output handler to parse the existing
|
||||
configuration data structure and prepare for output on the
|
||||
console and for saving it to configuration files
|
||||
|
||||
:param config: A dictionary containing configurations (basically archinstall.arguments)
|
||||
:type config: dict
|
||||
:param config: Archinstall configuration object
|
||||
:type config: ArchConfig
|
||||
"""
|
||||
|
||||
self._config = config
|
||||
self._user_credentials: dict[str, Any] = {}
|
||||
self._user_config: dict[str, Any] = {}
|
||||
self._default_save_path = storage.get('LOG_PATH', Path('.'))
|
||||
self._user_config_file = 'user_configuration.json'
|
||||
self._user_creds_file = "user_credentials.json"
|
||||
|
||||
self._sensitive = ['!users', '!root-password']
|
||||
self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run']
|
||||
|
||||
self._process_config()
|
||||
self._user_config_file = Path('user_configuration.json')
|
||||
self._user_creds_file = Path('user_credentials.json')
|
||||
|
||||
@property
|
||||
def user_credentials_file(self) -> str:
|
||||
return self._user_creds_file
|
||||
|
||||
@property
|
||||
def user_configuration_file(self) -> str:
|
||||
def user_configuration_file(self) -> Path:
|
||||
return self._user_config_file
|
||||
|
||||
def _process_config(self) -> None:
|
||||
for key, value in self._config.items():
|
||||
if key in self._sensitive:
|
||||
self._user_credentials[key] = value
|
||||
elif key in self._ignore:
|
||||
pass
|
||||
else:
|
||||
self._user_config[key] = value
|
||||
|
||||
# special handling for encryption password
|
||||
if key == 'disk_encryption' and value:
|
||||
self._user_credentials['encryption_password'] = value.encryption_password
|
||||
@property
|
||||
def user_credentials_file(self) -> Path:
|
||||
return self._user_creds_file
|
||||
|
||||
def user_config_to_json(self) -> str:
|
||||
return json.dumps({
|
||||
'config_version': storage['__version__'], # Tells us what version was used to generate the config
|
||||
**self._user_config, # __version__ will be overwritten by old version definition found in config
|
||||
'version': storage['__version__']
|
||||
}, indent=4, sort_keys=True, cls=JSON)
|
||||
out = self._config.safe_json()
|
||||
return json.dumps(out, indent=4, sort_keys=True, cls=JSON)
|
||||
|
||||
def user_credentials_to_json(self) -> str | None:
|
||||
if self._user_credentials:
|
||||
return json.dumps(self._user_credentials, indent=4, sort_keys=True, cls=UNSAFE_JSON)
|
||||
return None
|
||||
def user_credentials_to_json(self) -> str:
|
||||
out = self._config.unsafe_json()
|
||||
return json.dumps(out, indent=4, sort_keys=True, cls=UNSAFE_JSON)
|
||||
|
||||
def write_debug(self) -> None:
|
||||
debug(" -- Chosen configuration --")
|
||||
|
|
@ -120,10 +99,9 @@ class ConfigurationOutput:
|
|||
|
||||
def save_user_creds(self, dest_path: Path) -> None:
|
||||
if self._is_valid_path(dest_path):
|
||||
if user_creds := self.user_credentials_to_json():
|
||||
target = dest_path / self._user_creds_file
|
||||
target.write_text(user_creds)
|
||||
target.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||
target = dest_path / self._user_creds_file
|
||||
target.write_text(self.user_credentials_to_json())
|
||||
target.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||
|
||||
def save(self, dest_path: Path | None = None) -> None:
|
||||
save_path = dest_path or self._default_save_path
|
||||
|
|
@ -133,7 +111,7 @@ class ConfigurationOutput:
|
|||
self.save_user_creds(save_path)
|
||||
|
||||
|
||||
def save_config(config: dict[str, Any]) -> None:
|
||||
def save_config(config: ArchConfig) -> None:
|
||||
def preview(item: MenuItem) -> str | None:
|
||||
match item.value:
|
||||
case "user_config":
|
||||
|
|
@ -144,9 +122,9 @@ def save_config(config: dict[str, Any]) -> None:
|
|||
return f"{config_output.user_credentials_file}\n{maybe_serial}"
|
||||
return str(_("No configuration"))
|
||||
case "all":
|
||||
output = [config_output.user_configuration_file]
|
||||
if config_output.user_credentials_to_json():
|
||||
output.append(config_output.user_credentials_file)
|
||||
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 None
|
||||
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
from .device_handler import device_handler, disk_layouts
|
||||
from .device_model import (
|
||||
BDevice,
|
||||
DeviceGeometry,
|
||||
DeviceModification,
|
||||
DiskEncryption,
|
||||
DiskLayoutConfiguration,
|
||||
DiskLayoutType,
|
||||
EncryptionType,
|
||||
Fido2Device,
|
||||
FilesystemType,
|
||||
LsblkInfo,
|
||||
LvmConfiguration,
|
||||
LvmLayoutType,
|
||||
LvmVolume,
|
||||
LvmVolumeGroup,
|
||||
LvmVolumeStatus,
|
||||
ModificationStatus,
|
||||
PartitionFlag,
|
||||
PartitionModification,
|
||||
PartitionTable,
|
||||
PartitionType,
|
||||
SectorSize,
|
||||
Size,
|
||||
SubvolumeModification,
|
||||
Unit,
|
||||
_DeviceInfo,
|
||||
get_all_lsblk_info,
|
||||
get_lsblk_by_mountpoint,
|
||||
get_lsblk_info,
|
||||
)
|
||||
from .disk_menu import DiskLayoutConfigurationMenu
|
||||
from .encryption_menu import (
|
||||
DiskEncryptionMenu,
|
||||
select_encrypted_password,
|
||||
select_encryption_type,
|
||||
select_hsm,
|
||||
select_partitions_to_encrypt,
|
||||
)
|
||||
from .fido import Fido2
|
||||
from .filesystem import FilesystemHandler
|
||||
from .partitioning_menu import PartitioningList, manual_partitioning
|
||||
from .subvolume_menu import SubvolumeMenu
|
||||
|
|
@ -13,9 +13,7 @@ from parted import Device, Disk, DiskException, FileSystem, Geometry, IOExceptio
|
|||
from ..exceptions import DiskError, UnknownFilesystemFormat
|
||||
from ..general import SysCallError, SysCommand, SysCommandWorker
|
||||
from ..luks import Luks2
|
||||
from ..output import debug, error, info, log, warn
|
||||
from ..utils.util import is_subpath
|
||||
from .device_model import (
|
||||
from ..models.device_model import (
|
||||
BDevice,
|
||||
BtrfsMountOption,
|
||||
DeviceModification,
|
||||
|
|
@ -39,10 +37,13 @@ from .device_model import (
|
|||
_BtrfsSubvolumeInfo,
|
||||
_DeviceInfo,
|
||||
_PartitionInfo,
|
||||
)
|
||||
from ..output import debug, error, info, log
|
||||
from ..utils.util import is_subpath
|
||||
from .utils import (
|
||||
find_lsblk_info,
|
||||
get_all_lsblk_info,
|
||||
get_lsblk_info,
|
||||
get_lsblk_output,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -866,13 +867,3 @@ class DeviceHandler:
|
|||
|
||||
|
||||
device_handler = DeviceHandler()
|
||||
|
||||
|
||||
def disk_layouts() -> str:
|
||||
try:
|
||||
lsblk_output = get_lsblk_output()
|
||||
except SysCallError as err:
|
||||
warn(f"Could not return disk layouts: {err}")
|
||||
return ''
|
||||
|
||||
return lsblk_output.model_dump_json(indent=4)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
from typing import TYPE_CHECKING, Any, override
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.lib.models.device_model import DiskLayoutConfiguration, DiskLayoutType, LvmConfiguration
|
||||
from archinstall.tui import MenuItem, MenuItemGroup
|
||||
|
||||
from ..interactions import select_disk_config
|
||||
from ..interactions.disk_conf import select_lvm_config
|
||||
from ..menu import AbstractSubMenu
|
||||
from ..output import FormattedOutput
|
||||
from . import DiskLayoutConfiguration, DiskLayoutType
|
||||
from .device_model import LvmConfiguration
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
|
@ -17,34 +17,44 @@ if TYPE_CHECKING:
|
|||
_: Callable[[str], DeferredTranslation]
|
||||
|
||||
|
||||
@dataclass
|
||||
class DiskMenuConfig:
|
||||
disk_config: DiskLayoutConfiguration | None
|
||||
lvm_config: LvmConfiguration | None
|
||||
|
||||
|
||||
class DiskLayoutConfigurationMenu(AbstractSubMenu):
|
||||
def __init__(
|
||||
self,
|
||||
disk_layout_config: DiskLayoutConfiguration | None,
|
||||
advanced: bool = False
|
||||
):
|
||||
self._disk_layout_config = disk_layout_config
|
||||
self._advanced = advanced
|
||||
self._data_store: dict[str, Any] = {}
|
||||
def __init__(self, disk_layout_config: DiskLayoutConfiguration | None):
|
||||
if not disk_layout_config:
|
||||
self._disk_menu_config = DiskMenuConfig(disk_config=None, lvm_config=None)
|
||||
else:
|
||||
self._disk_menu_config = DiskMenuConfig(
|
||||
disk_config=disk_layout_config,
|
||||
lvm_config=disk_layout_config.lvm_config
|
||||
)
|
||||
|
||||
menu_optioons = self._define_menu_options()
|
||||
self._item_group = MenuItemGroup(menu_optioons, sort_items=False, checkmarks=True)
|
||||
|
||||
super().__init__(self._item_group, data_store=self._data_store, allow_reset=True)
|
||||
super().__init__(
|
||||
self._item_group,
|
||||
self._disk_menu_config,
|
||||
allow_reset=True
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=str(_('Partitioning')),
|
||||
action=self._select_disk_layout_config,
|
||||
value=self._disk_layout_config,
|
||||
value=self._disk_menu_config.disk_config,
|
||||
preview_action=self._prev_disk_layouts,
|
||||
key='disk_config'
|
||||
),
|
||||
MenuItem(
|
||||
text='LVM (BETA)',
|
||||
action=self._select_lvm_config,
|
||||
value=self._disk_layout_config.lvm_config if self._disk_layout_config else None,
|
||||
value=self._disk_menu_config.lvm_config,
|
||||
preview_action=self._prev_lvm_config,
|
||||
dependencies=[self._check_dep_lvm],
|
||||
key='lvm_config'
|
||||
|
|
@ -55,12 +65,11 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu):
|
|||
def run(self) -> DiskLayoutConfiguration | None:
|
||||
super().run()
|
||||
|
||||
disk_layout_config: DiskLayoutConfiguration | None = self._data_store.get('disk_config', None)
|
||||
if self._disk_menu_config.disk_config:
|
||||
self._disk_menu_config.disk_config.lvm_config = self._disk_menu_config.lvm_config
|
||||
return self._disk_menu_config.disk_config
|
||||
|
||||
if disk_layout_config:
|
||||
disk_layout_config.lvm_config = self._data_store.get('lvm_config', None)
|
||||
|
||||
return disk_layout_config
|
||||
return None
|
||||
|
||||
def _check_dep_lvm(self) -> bool:
|
||||
disk_layout_conf: DiskLayoutConfiguration | None = self._menu_item_group.find_by_key('disk_config').value
|
||||
|
|
@ -74,7 +83,7 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu):
|
|||
self,
|
||||
preset: DiskLayoutConfiguration | None
|
||||
) -> DiskLayoutConfiguration | None:
|
||||
disk_config = select_disk_config(preset, advanced_option=self._advanced)
|
||||
disk_config = select_disk_config(preset)
|
||||
|
||||
if disk_config != preset:
|
||||
self._menu_item_group.find_by_key('lvm_config').value = None
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.lib.menu.menu_helper import MenuHelper
|
||||
from archinstall.lib.models.device_model import (
|
||||
DeviceModification,
|
||||
DiskEncryption,
|
||||
DiskLayoutConfiguration,
|
||||
EncryptionType,
|
||||
LvmConfiguration,
|
||||
LvmVolume,
|
||||
PartitionModification,
|
||||
)
|
||||
from archinstall.tui import Alignment, FrameProperties, MenuItem, MenuItemGroup, ResultType, SelectMenu
|
||||
|
||||
from ..disk import DeviceModification, DiskEncryption, DiskLayoutConfiguration, EncryptionType, PartitionModification
|
||||
from ..menu import AbstractSubMenu
|
||||
from ..output import FormattedOutput
|
||||
from ..utils.util import get_password
|
||||
from . import LvmConfiguration, LvmVolume
|
||||
from .fido import Fido2, Fido2Device
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -26,31 +33,34 @@ class DiskEncryptionMenu(AbstractSubMenu):
|
|||
preset: DiskEncryption | None = None
|
||||
):
|
||||
if preset:
|
||||
self._preset = preset
|
||||
self._enc_config = preset
|
||||
else:
|
||||
self._preset = DiskEncryption()
|
||||
self._enc_config = DiskEncryption()
|
||||
|
||||
self._data_store: dict[str, Any] = {}
|
||||
self._disk_config = disk_config
|
||||
|
||||
menu_optioons = self._define_menu_options()
|
||||
self._item_group = MenuItemGroup(menu_optioons, sort_items=False, checkmarks=True)
|
||||
|
||||
super().__init__(self._item_group, data_store=self._data_store, allow_reset=True)
|
||||
super().__init__(
|
||||
self._item_group,
|
||||
self._enc_config,
|
||||
allow_reset=True
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=str(_('Encryption type')),
|
||||
action=lambda x: select_encryption_type(self._disk_config, x),
|
||||
value=self._preset.encryption_type,
|
||||
value=self._enc_config.encryption_type,
|
||||
preview_action=self._preview,
|
||||
key='encryption_type'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Encryption password')),
|
||||
action=lambda x: select_encrypted_password(),
|
||||
value=self._preset.encryption_password,
|
||||
value=self._enc_config.encryption_password,
|
||||
dependencies=[self._check_dep_enc_type],
|
||||
preview_action=self._preview,
|
||||
key='encryption_password'
|
||||
|
|
@ -58,7 +68,7 @@ class DiskEncryptionMenu(AbstractSubMenu):
|
|||
MenuItem(
|
||||
text=str(_('Partitions')),
|
||||
action=lambda x: select_partitions_to_encrypt(self._disk_config.device_modifications, x),
|
||||
value=self._preset.partitions,
|
||||
value=self._enc_config.partitions,
|
||||
dependencies=[self._check_dep_partitions],
|
||||
preview_action=self._preview,
|
||||
key='partitions'
|
||||
|
|
@ -66,18 +76,18 @@ class DiskEncryptionMenu(AbstractSubMenu):
|
|||
MenuItem(
|
||||
text=str(_('LVM volumes')),
|
||||
action=self._select_lvm_vols,
|
||||
value=self._preset.lvm_volumes,
|
||||
value=self._enc_config.lvm_volumes,
|
||||
dependencies=[self._check_dep_lvm_vols],
|
||||
preview_action=self._preview,
|
||||
key='lvm_vols'
|
||||
key='lvm_volumes'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('HSM')),
|
||||
action=select_hsm,
|
||||
value=self._preset.hsm_device,
|
||||
value=self._enc_config.hsm_device,
|
||||
dependencies=[self._check_dep_enc_type],
|
||||
preview_action=self._preview,
|
||||
key='HSM'
|
||||
key='hsm_device'
|
||||
),
|
||||
]
|
||||
|
||||
|
|
@ -111,7 +121,7 @@ class DiskEncryptionMenu(AbstractSubMenu):
|
|||
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
|
||||
enc_password: str | 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_vols').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
|
||||
|
|
@ -129,7 +139,7 @@ class DiskEncryptionMenu(AbstractSubMenu):
|
|||
encryption_type=enc_type,
|
||||
partitions=enc_partitions,
|
||||
lvm_volumes=enc_lvm_vols,
|
||||
hsm_device=self._data_store.get('HSM', None)
|
||||
hsm_device=self._enc_config.hsm_device
|
||||
)
|
||||
|
||||
return None
|
||||
|
|
@ -186,7 +196,7 @@ class DiskEncryptionMenu(AbstractSubMenu):
|
|||
return None
|
||||
|
||||
def _prev_lvm_vols(self) -> str | None:
|
||||
volumes: list[PartitionModification] | None = self._item_group.find_by_key('lvm_vols').value
|
||||
volumes: list[PartitionModification] | None = self._item_group.find_by_key('lvm_volumes').value
|
||||
|
||||
if volumes:
|
||||
output = str(_('LVM volumes to be encrypted')) + '\n'
|
||||
|
|
@ -196,7 +206,7 @@ class DiskEncryptionMenu(AbstractSubMenu):
|
|||
return None
|
||||
|
||||
def _prev_hsm(self) -> str | None:
|
||||
fido_device: Fido2Device | None = self._item_group.find_by_key('HSM').value
|
||||
fido_device: Fido2Device | None = self._item_group.find_by_key('hsm_device').value
|
||||
|
||||
if not fido_device:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import getpass
|
|||
from pathlib import Path
|
||||
from typing import ClassVar
|
||||
|
||||
from archinstall.lib.models.device_model import Fido2Device
|
||||
|
||||
from ..exceptions import SysCallError
|
||||
from ..general import SysCommand, SysCommandWorker, clear_vt100_escape_codes
|
||||
from ..output import error, info
|
||||
from .device_model import Fido2Device
|
||||
|
||||
|
||||
class Fido2:
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@ from archinstall.tui import Tui
|
|||
from ..hardware import SysInfo
|
||||
from ..interactions.general_conf import ask_abort
|
||||
from ..luks import Luks2
|
||||
from ..output import debug, info
|
||||
from .device_handler import device_handler
|
||||
from .device_model import (
|
||||
from ..models.device_model import (
|
||||
DiskEncryption,
|
||||
DiskLayoutConfiguration,
|
||||
DiskLayoutType,
|
||||
|
|
@ -26,6 +24,8 @@ from .device_model import (
|
|||
Size,
|
||||
Unit,
|
||||
)
|
||||
from ..output import debug, info
|
||||
from .device_handler import device_handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
|
|
|||
|
|
@ -4,13 +4,7 @@ import re
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.tui import Alignment, EditMenu, FrameProperties, MenuItem, MenuItemGroup, Orientation, ResultType, SelectMenu
|
||||
|
||||
from ..hardware import SysInfo
|
||||
from ..menu import ListManager
|
||||
from ..output import FormattedOutput
|
||||
from ..utils.util import prompt_dir
|
||||
from .device_model import (
|
||||
from archinstall.lib.models.device_model import (
|
||||
BDevice,
|
||||
BtrfsMountOption,
|
||||
FilesystemType,
|
||||
|
|
@ -22,6 +16,12 @@ from .device_model import (
|
|||
Size,
|
||||
Unit,
|
||||
)
|
||||
from archinstall.tui import Alignment, EditMenu, FrameProperties, MenuItem, MenuItemGroup, Orientation, ResultType, SelectMenu
|
||||
|
||||
from ..hardware import SysInfo
|
||||
from ..menu import ListManager
|
||||
from ..output import FormattedOutput
|
||||
from ..utils.util import prompt_dir
|
||||
from .subvolume_menu import SubvolumeMenu
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.lib.models.device_model import SubvolumeModification
|
||||
from archinstall.tui import Alignment, EditMenu, ResultType
|
||||
|
||||
from ..menu import ListManager
|
||||
from ..utils.util import prompt_dir
|
||||
from .device_model import SubvolumeModification
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
from pathlib import Path
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from archinstall.lib.exceptions import DiskError, SysCallError
|
||||
from archinstall.lib.general import SysCommand
|
||||
from archinstall.lib.models.device_model import LsblkInfo
|
||||
from archinstall.lib.output import debug, warn
|
||||
|
||||
|
||||
class LsblkOutput(BaseModel):
|
||||
blockdevices: list[LsblkInfo]
|
||||
|
||||
|
||||
def _fetch_lsblk_info(
|
||||
dev_path: Path | str | None = None,
|
||||
reverse: bool = False,
|
||||
full_dev_path: bool = False
|
||||
) -> LsblkOutput:
|
||||
cmd = ['lsblk', '--json', '--bytes', '--output', ','.join(LsblkInfo.fields())]
|
||||
|
||||
if reverse:
|
||||
cmd.append('--inverse')
|
||||
|
||||
if full_dev_path:
|
||||
cmd.append('--paths')
|
||||
|
||||
if dev_path:
|
||||
cmd.append(str(dev_path))
|
||||
|
||||
try:
|
||||
worker = SysCommand(cmd)
|
||||
except SysCallError as err:
|
||||
# Get the output minus the message/info from lsblk if it returns a non-zero exit code.
|
||||
if err.worker:
|
||||
err_str = err.worker.decode()
|
||||
debug(f'Error calling lsblk: {err_str}')
|
||||
|
||||
if dev_path:
|
||||
raise DiskError(f'Failed to read disk "{dev_path}" with lsblk')
|
||||
|
||||
raise err
|
||||
|
||||
output = worker.output(remove_cr=False)
|
||||
return LsblkOutput.model_validate_json(output)
|
||||
|
||||
|
||||
def get_lsblk_info(
|
||||
dev_path: Path | str,
|
||||
reverse: bool = False,
|
||||
full_dev_path: bool = False
|
||||
) -> LsblkInfo:
|
||||
infos = _fetch_lsblk_info(dev_path, reverse=reverse, full_dev_path=full_dev_path)
|
||||
|
||||
if infos.blockdevices:
|
||||
return infos.blockdevices[0]
|
||||
|
||||
raise DiskError(f'lsblk failed to retrieve information for "{dev_path}"')
|
||||
|
||||
|
||||
def get_all_lsblk_info() -> list[LsblkInfo]:
|
||||
return _fetch_lsblk_info().blockdevices
|
||||
|
||||
|
||||
def get_lsblk_output() -> LsblkOutput:
|
||||
return _fetch_lsblk_info()
|
||||
|
||||
|
||||
def find_lsblk_info(
|
||||
dev_path: Path | str,
|
||||
info: list[LsblkInfo]
|
||||
) -> LsblkInfo | None:
|
||||
if isinstance(dev_path, str):
|
||||
dev_path = Path(dev_path)
|
||||
|
||||
for lsblk_info in info:
|
||||
if lsblk_info.path == dev_path:
|
||||
return lsblk_info
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_lsblk_by_mountpoint(mountpoint: Path, as_prefix: bool = False) -> list[LsblkInfo]:
|
||||
def _check(infos: list[LsblkInfo]) -> list[LsblkInfo]:
|
||||
devices = []
|
||||
for entry in infos:
|
||||
if as_prefix:
|
||||
matches = [m for m in entry.mountpoints if str(m).startswith(str(mountpoint))]
|
||||
if matches:
|
||||
devices += [entry]
|
||||
elif mountpoint in entry.mountpoints:
|
||||
devices += [entry]
|
||||
|
||||
if len(entry.children) > 0:
|
||||
if len(match := _check(entry.children)) > 0:
|
||||
devices += match
|
||||
|
||||
return devices
|
||||
|
||||
all_info = get_all_lsblk_info()
|
||||
return _check(all_info)
|
||||
|
||||
|
||||
def disk_layouts() -> str:
|
||||
try:
|
||||
lsblk_output = get_lsblk_output()
|
||||
except SysCallError as err:
|
||||
warn(f"Could not return disk layouts: {err}")
|
||||
return ''
|
||||
|
||||
return lsblk_output.model_dump_json(indent=4)
|
||||
|
|
@ -319,8 +319,7 @@ class SysCommandWorker:
|
|||
# If history_logfile does not exist, ignore the error
|
||||
pass
|
||||
|
||||
if storage.get('arguments', {}).get('debug'):
|
||||
debug(f"Executing: {self.cmd}")
|
||||
debug(f"Executing: {" ".join(self.cmd)}")
|
||||
|
||||
try:
|
||||
os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
|
||||
from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu
|
||||
from archinstall.lib.models.device_model import DiskEncryption, DiskLayoutConfiguration, DiskLayoutType, EncryptionType, FilesystemType, PartitionModification
|
||||
from archinstall.tui import MenuItem, MenuItemGroup
|
||||
|
||||
from . import disk
|
||||
from .args import ArchConfig
|
||||
from .configuration import save_config
|
||||
from .general import secret
|
||||
from .hardware import SysInfo
|
||||
|
|
@ -23,12 +26,13 @@ from .interactions import (
|
|||
select_additional_repositories,
|
||||
select_kernel,
|
||||
)
|
||||
from .locale.locale_menu import LocaleConfiguration, LocaleMenu
|
||||
from .locale.locale_menu import LocaleMenu
|
||||
from .menu import AbstractMenu
|
||||
from .mirrors import MirrorConfiguration, MirrorMenu
|
||||
from .models import NetworkConfiguration, NicType
|
||||
from .models.audio_configuration import AudioConfiguration
|
||||
from .models.bootloader import Bootloader
|
||||
from .models.locale import LocaleConfiguration
|
||||
from .models.users import User
|
||||
from .output import FormattedOutput
|
||||
from .profile.profile_menu import ProfileConfiguration
|
||||
|
|
@ -44,20 +48,17 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class GlobalMenu(AbstractMenu):
|
||||
def __init__(self, data_store: dict[str, Any]):
|
||||
self._data_store = data_store
|
||||
|
||||
if 'archinstall-language' not in data_store:
|
||||
data_store['archinstall-language'] = translation_handler.get_language_by_abbr('en')
|
||||
|
||||
def __init__(self, arch_config: ArchConfig) -> None:
|
||||
self._arch_config = arch_config
|
||||
menu_optioons = self._get_menu_options()
|
||||
|
||||
self._item_group = MenuItemGroup(
|
||||
menu_optioons,
|
||||
sort_items=False,
|
||||
checkmarks=True
|
||||
)
|
||||
|
||||
super().__init__(self._item_group, data_store)
|
||||
super().__init__(self._item_group, config=arch_config)
|
||||
|
||||
def _get_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
|
|
@ -65,7 +66,7 @@ class GlobalMenu(AbstractMenu):
|
|||
text=str(_('Archinstall language')),
|
||||
action=self._select_archinstall_language,
|
||||
display_action=lambda x: x.display_name if x else '',
|
||||
key='archinstall-language'
|
||||
key='archinstall_language'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Locales')),
|
||||
|
|
@ -90,8 +91,8 @@ class GlobalMenu(AbstractMenu):
|
|||
text=str(_('Disk encryption')),
|
||||
action=self._disk_encryption,
|
||||
preview_action=self._prev_disk_encryption,
|
||||
key='disk_encryption',
|
||||
dependencies=['disk_config']
|
||||
dependencies=['disk_config'],
|
||||
key='disk_encryption'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Swap')),
|
||||
|
|
@ -126,13 +127,13 @@ class GlobalMenu(AbstractMenu):
|
|||
text=str(_('Root password')),
|
||||
action=self._set_root_password,
|
||||
preview_action=self._prev_root_pwd,
|
||||
key='!root-password',
|
||||
key='root_password',
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('User account')),
|
||||
action=self._create_user_account,
|
||||
preview_action=self._prev_users,
|
||||
key='!users'
|
||||
key='users'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Profile')),
|
||||
|
|
@ -166,7 +167,7 @@ class GlobalMenu(AbstractMenu):
|
|||
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')),
|
||||
|
|
@ -180,7 +181,7 @@ class GlobalMenu(AbstractMenu):
|
|||
action=select_additional_repositories,
|
||||
value=[],
|
||||
preview_action=self._prev_additional_repos,
|
||||
key='additional-repositories'
|
||||
key='additional_repositories'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Timezone')),
|
||||
|
|
@ -202,27 +203,28 @@ class GlobalMenu(AbstractMenu):
|
|||
MenuItem(
|
||||
text=str(_('Save configuration')),
|
||||
action=lambda x: self._safe_config(),
|
||||
key='save_config'
|
||||
key='__config__'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Install')),
|
||||
preview_action=self._prev_install_invalid_config,
|
||||
key='install'
|
||||
key='__config__'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Abort')),
|
||||
action=lambda x: exit(1),
|
||||
key='abort'
|
||||
key='__config__'
|
||||
)
|
||||
]
|
||||
|
||||
def _safe_config(self) -> None:
|
||||
data: dict[str, Any] = {}
|
||||
for item in self._item_group.items:
|
||||
if item.key is not None:
|
||||
data[item.key] = item.value
|
||||
# data: dict[str, Any] = {}
|
||||
# for item in self._item_group.items:
|
||||
# if item.key is not None:
|
||||
# data[item.key] = item.value
|
||||
|
||||
save_config(data)
|
||||
self.sync_all_to_config()
|
||||
save_config(self._arch_config)
|
||||
|
||||
def _missing_configs(self) -> list[str]:
|
||||
def check(s) -> bool:
|
||||
|
|
@ -280,17 +282,17 @@ class GlobalMenu(AbstractMenu):
|
|||
if o.key is not None:
|
||||
self._item_group.find_by_key(o.key).text = o.text
|
||||
|
||||
def _disk_encryption(self, preset: disk.DiskEncryption | None) -> disk.DiskEncryption | None:
|
||||
disk_config: disk.DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
|
||||
def _disk_encryption(self, preset: DiskEncryption | None) -> DiskEncryption | None:
|
||||
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')
|
||||
|
||||
if not disk.DiskEncryption.validate_enc(disk_config):
|
||||
if not DiskEncryption.validate_enc(disk_config):
|
||||
return None
|
||||
|
||||
disk_encryption = disk.DiskEncryptionMenu(disk_config, preset=preset).run()
|
||||
disk_encryption = DiskEncryptionMenu(disk_config, preset=preset).run()
|
||||
return disk_encryption
|
||||
|
||||
def _locale_selection(self, preset: LocaleConfiguration) -> LocaleConfiguration:
|
||||
|
|
@ -339,12 +341,12 @@ class GlobalMenu(AbstractMenu):
|
|||
return None
|
||||
|
||||
def _prev_disk_config(self, item: MenuItem) -> str | None:
|
||||
disk_layout_conf: disk.DiskLayoutConfiguration | None = item.value
|
||||
disk_layout_conf: DiskLayoutConfiguration | None = item.value
|
||||
|
||||
if disk_layout_conf:
|
||||
output = str(_('Configuration type: {}')).format(disk_layout_conf.config_type.display_msg()) + '\n'
|
||||
|
||||
if disk_layout_conf.config_type == disk.DiskLayoutType.Pre_mount:
|
||||
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
|
||||
output += str(_('Mountpoint')) + ': ' + str(disk_layout_conf.mountpoint)
|
||||
|
||||
if disk_layout_conf.lvm_config:
|
||||
|
|
@ -401,14 +403,14 @@ class GlobalMenu(AbstractMenu):
|
|||
return None
|
||||
|
||||
def _prev_disk_encryption(self, item: MenuItem) -> str | None:
|
||||
disk_config: disk.DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
|
||||
enc_config: disk.DiskEncryption | None = item.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 disk.DiskEncryption.validate_enc(disk_config):
|
||||
if disk_config and not DiskEncryption.validate_enc(disk_config):
|
||||
return str(_('LVM disk encryption with more than 2 partitions is currently not supported'))
|
||||
|
||||
if enc_config:
|
||||
enc_type = disk.EncryptionType.type_to_text(enc_config.encryption_type)
|
||||
enc_type = EncryptionType.type_to_text(enc_config.encryption_type)
|
||||
output = str(_('Encryption type')) + f': {enc_type}\n'
|
||||
output += str(_('Password')) + f': {secret(enc_config.encryption_password)}\n'
|
||||
|
||||
|
|
@ -436,7 +438,7 @@ class GlobalMenu(AbstractMenu):
|
|||
shim if necessary.
|
||||
"""
|
||||
bootloader = self._item_group.find_by_key('bootloader').value
|
||||
boot_partition: disk.PartitionModification | None = None
|
||||
boot_partition: PartitionModification | None = None
|
||||
|
||||
if disk_config := self._item_group.find_by_key('disk_config').value:
|
||||
for layout in disk_config.device_modifications:
|
||||
|
|
@ -449,7 +451,7 @@ class GlobalMenu(AbstractMenu):
|
|||
return "Boot partition not found"
|
||||
|
||||
if bootloader == Bootloader.Limine:
|
||||
if boot_partition.fs_type != disk.FilesystemType.Fat32:
|
||||
if boot_partition.fs_type != FilesystemType.Fat32:
|
||||
return "Limine does not support booting from filesystems other than FAT32"
|
||||
|
||||
return None
|
||||
|
|
@ -499,9 +501,9 @@ class GlobalMenu(AbstractMenu):
|
|||
|
||||
def _select_disk_config(
|
||||
self,
|
||||
preset: disk.DiskLayoutConfiguration | None = None
|
||||
) -> disk.DiskLayoutConfiguration | None:
|
||||
disk_config = disk.DiskLayoutConfigurationMenu(preset).run()
|
||||
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
|
||||
|
|
|
|||
|
|
@ -10,16 +10,33 @@ from pathlib import Path
|
|||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from archinstall.lib.disk.device_handler import device_handler
|
||||
from archinstall.lib.disk.fido import Fido2
|
||||
from archinstall.lib.disk.utils import get_lsblk_by_mountpoint, get_lsblk_info
|
||||
from archinstall.lib.models.device_model import (
|
||||
DiskEncryption,
|
||||
DiskLayoutConfiguration,
|
||||
EncryptionType,
|
||||
FilesystemType,
|
||||
LvmVolume,
|
||||
PartitionModification,
|
||||
SectorSize,
|
||||
Size,
|
||||
SubvolumeModification,
|
||||
Unit,
|
||||
)
|
||||
from archinstall.tui.curses_menu import Tui
|
||||
|
||||
from . import disk, pacman
|
||||
from . import pacman
|
||||
from .args import arch_config_handler
|
||||
from .exceptions import DiskError, HardwareIncompatibilityError, RequirementError, ServiceException, SysCallError
|
||||
from .general import SysCommand
|
||||
from .hardware import SysInfo
|
||||
from .locale import LocaleConfiguration, verify_keyboard_layout, verify_x11_keyboard_layout
|
||||
from .locale import verify_keyboard_layout, verify_x11_keyboard_layout
|
||||
from .luks import Luks2
|
||||
from .mirrors import MirrorConfiguration
|
||||
from .models.bootloader import Bootloader
|
||||
from .models.locale import LocaleConfiguration
|
||||
from .models.network_configuration import Nic
|
||||
from .models.users import User
|
||||
from .output import debug, error, info, log, warn
|
||||
|
|
@ -45,8 +62,8 @@ class Installer:
|
|||
def __init__(
|
||||
self,
|
||||
target: Path,
|
||||
disk_config: disk.DiskLayoutConfiguration,
|
||||
disk_encryption: disk.DiskEncryption | None = None,
|
||||
disk_config: DiskLayoutConfiguration,
|
||||
disk_encryption: DiskEncryption | None = None,
|
||||
base_packages: list[str] = [],
|
||||
kernels: list[str] | None = None
|
||||
):
|
||||
|
|
@ -58,7 +75,7 @@ class Installer:
|
|||
self.kernels = kernels or ['linux']
|
||||
self._disk_config = disk_config
|
||||
|
||||
self._disk_encryption = disk_encryption or disk.DiskEncryption(disk.EncryptionType.NoEncryption)
|
||||
self._disk_encryption = disk_encryption or DiskEncryption(EncryptionType.NoEncryption)
|
||||
self.target: Path = target
|
||||
|
||||
self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
|
||||
|
|
@ -94,7 +111,7 @@ class Installer:
|
|||
self._zram_enabled = False
|
||||
self._disable_fstrim = False
|
||||
|
||||
self.pacman = Pacman(self.target, storage['arguments'].get('silent', False))
|
||||
self.pacman = Pacman(self.target, arch_config_handler.args.silent)
|
||||
|
||||
def __enter__(self) -> 'Installer':
|
||||
return self
|
||||
|
|
@ -144,7 +161,7 @@ class Installer:
|
|||
We need to wait for it before we continue since we opted in to use a custom mirror/region.
|
||||
"""
|
||||
|
||||
if not storage['arguments'].get('skip_ntp', False):
|
||||
if not arch_config_handler.args.skip_ntp:
|
||||
info(_('Waiting for time sync (timedatectl show) to complete.'))
|
||||
|
||||
started_wait = time.time()
|
||||
|
|
@ -189,10 +206,10 @@ class Installer:
|
|||
NOTE: this function should be run AFTER running the mount_ordered_layout function
|
||||
"""
|
||||
boot_mount = self.target / 'boot'
|
||||
lsblk_info = disk.get_lsblk_by_mountpoint(boot_mount)
|
||||
lsblk_info = get_lsblk_by_mountpoint(boot_mount)
|
||||
|
||||
if len(lsblk_info) > 0:
|
||||
if lsblk_info[0].size < disk.Size(200, disk.Unit.MiB, disk.SectorSize.default()):
|
||||
if lsblk_info[0].size < Size(200, Unit.MiB, SectorSize.default()):
|
||||
raise DiskError(
|
||||
f'The boot partition mounted at {boot_mount} is not large enough to install a boot loader. '
|
||||
f'Please resize it to at least 200MiB and re-run the installation.'
|
||||
|
|
@ -208,15 +225,15 @@ class Installer:
|
|||
luks_handlers: dict[Any, Luks2] = {}
|
||||
|
||||
match self._disk_encryption.encryption_type:
|
||||
case disk.EncryptionType.NoEncryption:
|
||||
case EncryptionType.NoEncryption:
|
||||
self._mount_lvm_layout()
|
||||
case disk.EncryptionType.Luks:
|
||||
case EncryptionType.Luks:
|
||||
luks_handlers = self._prepare_luks_partitions(self._disk_encryption.partitions)
|
||||
case disk.EncryptionType.LvmOnLuks:
|
||||
case EncryptionType.LvmOnLuks:
|
||||
luks_handlers = self._prepare_luks_partitions(self._disk_encryption.partitions)
|
||||
self._import_lvm()
|
||||
self._mount_lvm_layout(luks_handlers)
|
||||
case disk.EncryptionType.LuksOnLvm:
|
||||
case EncryptionType.LuksOnLvm:
|
||||
self._import_lvm()
|
||||
luks_handlers = self._prepare_luks_lvm(self._disk_encryption.lvm_volumes)
|
||||
self._mount_lvm_layout(luks_handlers)
|
||||
|
|
@ -275,10 +292,10 @@ class Installer:
|
|||
|
||||
def _prepare_luks_partitions(
|
||||
self,
|
||||
partitions: list[disk.PartitionModification]
|
||||
) -> dict[disk.PartitionModification, Luks2]:
|
||||
partitions: list[PartitionModification]
|
||||
) -> dict[PartitionModification, Luks2]:
|
||||
return {
|
||||
part_mod: disk.device_handler.unlock_luks2_dev(
|
||||
part_mod: device_handler.unlock_luks2_dev(
|
||||
part_mod.dev_path,
|
||||
part_mod.mapper_name,
|
||||
self._disk_encryption.encryption_password
|
||||
|
|
@ -295,17 +312,17 @@ class Installer:
|
|||
return
|
||||
|
||||
for vg in lvm_config.vol_groups:
|
||||
disk.device_handler.lvm_import_vg(vg)
|
||||
device_handler.lvm_import_vg(vg)
|
||||
|
||||
for vol in vg.volumes:
|
||||
disk.device_handler.lvm_vol_change(vol, True)
|
||||
device_handler.lvm_vol_change(vol, True)
|
||||
|
||||
def _prepare_luks_lvm(
|
||||
self,
|
||||
lvm_volumes: list[disk.LvmVolume]
|
||||
) -> dict[disk.LvmVolume, Luks2]:
|
||||
lvm_volumes: list[LvmVolume]
|
||||
) -> dict[LvmVolume, Luks2]:
|
||||
return {
|
||||
vol: disk.device_handler.unlock_luks2_dev(
|
||||
vol: device_handler.unlock_luks2_dev(
|
||||
vol.dev_path,
|
||||
vol.mapper_name,
|
||||
self._disk_encryption.encryption_password
|
||||
|
|
@ -314,69 +331,69 @@ class Installer:
|
|||
if vol.mapper_name and vol.dev_path
|
||||
}
|
||||
|
||||
def _mount_partition(self, part_mod: disk.PartitionModification) -> None:
|
||||
def _mount_partition(self, part_mod: PartitionModification) -> None:
|
||||
if not part_mod.dev_path:
|
||||
return
|
||||
|
||||
# it would be none if it's btrfs as the subvolumes will have the mountpoints defined
|
||||
if part_mod.mountpoint:
|
||||
target = self.target / part_mod.relative_mountpoint
|
||||
disk.device_handler.mount(part_mod.dev_path, target, options=part_mod.mount_options)
|
||||
elif part_mod.fs_type == disk.FilesystemType.Btrfs:
|
||||
device_handler.mount(part_mod.dev_path, target, options=part_mod.mount_options)
|
||||
elif part_mod.fs_type == FilesystemType.Btrfs:
|
||||
self._mount_btrfs_subvol(
|
||||
part_mod.dev_path,
|
||||
part_mod.btrfs_subvols,
|
||||
part_mod.mount_options
|
||||
)
|
||||
elif part_mod.is_swap():
|
||||
disk.device_handler.swapon(part_mod.dev_path)
|
||||
device_handler.swapon(part_mod.dev_path)
|
||||
|
||||
def _mount_lvm_vol(self, volume: disk.LvmVolume) -> None:
|
||||
if volume.fs_type != disk.FilesystemType.Btrfs:
|
||||
def _mount_lvm_vol(self, volume: LvmVolume) -> None:
|
||||
if volume.fs_type != FilesystemType.Btrfs:
|
||||
if volume.mountpoint and volume.dev_path:
|
||||
target = self.target / volume.relative_mountpoint
|
||||
disk.device_handler.mount(volume.dev_path, target, options=volume.mount_options)
|
||||
device_handler.mount(volume.dev_path, target, options=volume.mount_options)
|
||||
|
||||
if volume.fs_type == disk.FilesystemType.Btrfs and volume.dev_path:
|
||||
if volume.fs_type == FilesystemType.Btrfs and volume.dev_path:
|
||||
self._mount_btrfs_subvol(volume.dev_path, volume.btrfs_subvols, volume.mount_options)
|
||||
|
||||
def _mount_luks_partition(self, part_mod: disk.PartitionModification, luks_handler: Luks2) -> None:
|
||||
def _mount_luks_partition(self, part_mod: PartitionModification, luks_handler: Luks2) -> None:
|
||||
if not luks_handler.mapper_dev:
|
||||
return None
|
||||
|
||||
if part_mod.fs_type == disk.FilesystemType.Btrfs and part_mod.btrfs_subvols:
|
||||
if part_mod.fs_type == FilesystemType.Btrfs and part_mod.btrfs_subvols:
|
||||
self._mount_btrfs_subvol(luks_handler.mapper_dev, part_mod.btrfs_subvols, part_mod.mount_options)
|
||||
elif part_mod.mountpoint:
|
||||
target = self.target / part_mod.relative_mountpoint
|
||||
disk.device_handler.mount(luks_handler.mapper_dev, target, options=part_mod.mount_options)
|
||||
device_handler.mount(luks_handler.mapper_dev, target, options=part_mod.mount_options)
|
||||
|
||||
def _mount_luks_volume(self, volume: disk.LvmVolume, luks_handler: Luks2) -> None:
|
||||
if volume.fs_type != disk.FilesystemType.Btrfs:
|
||||
def _mount_luks_volume(self, volume: LvmVolume, luks_handler: Luks2) -> None:
|
||||
if volume.fs_type != FilesystemType.Btrfs:
|
||||
if volume.mountpoint and luks_handler.mapper_dev:
|
||||
target = self.target / volume.relative_mountpoint
|
||||
disk.device_handler.mount(luks_handler.mapper_dev, target, options=volume.mount_options)
|
||||
device_handler.mount(luks_handler.mapper_dev, target, options=volume.mount_options)
|
||||
|
||||
if volume.fs_type == disk.FilesystemType.Btrfs and luks_handler.mapper_dev:
|
||||
if volume.fs_type == FilesystemType.Btrfs and luks_handler.mapper_dev:
|
||||
self._mount_btrfs_subvol(luks_handler.mapper_dev, volume.btrfs_subvols, volume.mount_options)
|
||||
|
||||
def _mount_btrfs_subvol(
|
||||
self,
|
||||
dev_path: Path,
|
||||
subvolumes: list[disk.SubvolumeModification],
|
||||
subvolumes: list[SubvolumeModification],
|
||||
mount_options: list[str] = []
|
||||
) -> None:
|
||||
for subvol in sorted(subvolumes, key=lambda x: x.relative_mountpoint):
|
||||
mountpoint = self.target / subvol.relative_mountpoint
|
||||
options = mount_options + [f'subvol={subvol.name}']
|
||||
disk.device_handler.mount(dev_path, mountpoint, options=options)
|
||||
device_handler.mount(dev_path, mountpoint, options=options)
|
||||
|
||||
def generate_key_files(self) -> None:
|
||||
match self._disk_encryption.encryption_type:
|
||||
case disk.EncryptionType.Luks:
|
||||
case EncryptionType.Luks:
|
||||
self._generate_key_files_partitions()
|
||||
case disk.EncryptionType.LuksOnLvm:
|
||||
case EncryptionType.LuksOnLvm:
|
||||
self._generate_key_file_lvm_volumes()
|
||||
case disk.EncryptionType.LvmOnLuks:
|
||||
case EncryptionType.LvmOnLuks:
|
||||
# currently LvmOnLuks only supports a single
|
||||
# partitioning layout (boot + partition)
|
||||
# so we won't need any keyfile generation atm
|
||||
|
|
@ -398,7 +415,7 @@ class Installer:
|
|||
|
||||
if part_mod.is_root() and not gen_enc_file:
|
||||
if self._disk_encryption.hsm_device:
|
||||
disk.Fido2.fido2_enroll(
|
||||
Fido2.fido2_enroll(
|
||||
self._disk_encryption.hsm_device,
|
||||
part_mod.safe_dev_path,
|
||||
self._disk_encryption.encryption_password
|
||||
|
|
@ -420,7 +437,7 @@ class Installer:
|
|||
|
||||
if vol.is_root() and not gen_enc_file:
|
||||
if self._disk_encryption.hsm_device:
|
||||
disk.Fido2.fido2_enroll(
|
||||
Fido2.fido2_enroll(
|
||||
self._disk_encryption.hsm_device,
|
||||
vol.safe_dev_path,
|
||||
self._disk_encryption.encryption_password
|
||||
|
|
@ -742,7 +759,7 @@ class Installer:
|
|||
|
||||
def _prepare_fs_type(
|
||||
self,
|
||||
fs_type: disk.FilesystemType,
|
||||
fs_type: FilesystemType,
|
||||
mountpoint: Path | None
|
||||
) -> None:
|
||||
if (pkg := fs_type.installation_pkg) is not None:
|
||||
|
|
@ -778,7 +795,7 @@ class Installer:
|
|||
multilib: bool = False,
|
||||
mkinitcpio: bool = True,
|
||||
hostname: str | None = None,
|
||||
locale_config: LocaleConfiguration = LocaleConfiguration.default()
|
||||
locale_config: LocaleConfiguration | None = LocaleConfiguration.default()
|
||||
):
|
||||
if self._disk_config.lvm_config:
|
||||
lvm = 'lvm2'
|
||||
|
|
@ -790,7 +807,7 @@ class Installer:
|
|||
if vol.fs_type is not None:
|
||||
self._prepare_fs_type(vol.fs_type, vol.mountpoint)
|
||||
|
||||
types = (disk.EncryptionType.LvmOnLuks, disk.EncryptionType.LuksOnLvm)
|
||||
types = (EncryptionType.LvmOnLuks, EncryptionType.LuksOnLvm)
|
||||
if self._disk_encryption.encryption_type in types:
|
||||
self._prepare_encrypt(lvm)
|
||||
else:
|
||||
|
|
@ -852,8 +869,9 @@ class Installer:
|
|||
if hostname:
|
||||
self.set_hostname(hostname)
|
||||
|
||||
self.set_locale(locale_config)
|
||||
self.set_keyboard_language(locale_config.kb_layout)
|
||||
if locale_config:
|
||||
self.set_locale(locale_config)
|
||||
self.set_keyboard_language(locale_config.kb_layout)
|
||||
|
||||
# TODO: Use python functions for this
|
||||
SysCommand(f'arch-chroot {self.target} chmod 700 /root')
|
||||
|
|
@ -889,19 +907,19 @@ class Installer:
|
|||
else:
|
||||
raise ValueError("Archinstall currently only supports setting up swap on zram")
|
||||
|
||||
def _get_efi_partition(self) -> disk.PartitionModification | None:
|
||||
def _get_efi_partition(self) -> PartitionModification | None:
|
||||
for layout in self._disk_config.device_modifications:
|
||||
if partition := layout.get_efi_partition():
|
||||
return partition
|
||||
return None
|
||||
|
||||
def _get_boot_partition(self) -> disk.PartitionModification | None:
|
||||
def _get_boot_partition(self) -> PartitionModification | None:
|
||||
for layout in self._disk_config.device_modifications:
|
||||
if boot := layout.get_boot_partition():
|
||||
return boot
|
||||
return None
|
||||
|
||||
def _get_root(self) -> disk.PartitionModification | disk.LvmVolume | None:
|
||||
def _get_root(self) -> PartitionModification | LvmVolume | None:
|
||||
if self._disk_config.lvm_config:
|
||||
return self._disk_config.lvm_config.get_root_volume()
|
||||
else:
|
||||
|
|
@ -911,7 +929,7 @@ class Installer:
|
|||
return None
|
||||
|
||||
def _get_luks_uuid_from_mapper_dev(self, mapper_dev_path: Path) -> str:
|
||||
lsblk_info = disk.get_lsblk_info(mapper_dev_path, reverse=True, full_dev_path=True)
|
||||
lsblk_info = get_lsblk_info(mapper_dev_path, reverse=True, full_dev_path=True)
|
||||
|
||||
if not lsblk_info.children or not lsblk_info.children[0].uuid:
|
||||
raise ValueError('Unable to determine UUID of luks superblock')
|
||||
|
|
@ -920,7 +938,7 @@ class Installer:
|
|||
|
||||
def _get_kernel_params_partition(
|
||||
self,
|
||||
root_partition: disk.PartitionModification,
|
||||
root_partition: PartitionModification,
|
||||
id_root: bool = True,
|
||||
partuuid: bool = True
|
||||
) -> list[str]:
|
||||
|
|
@ -958,16 +976,16 @@ class Installer:
|
|||
|
||||
def _get_kernel_params_lvm(
|
||||
self,
|
||||
lvm: disk.LvmVolume
|
||||
lvm: LvmVolume
|
||||
) -> list[str]:
|
||||
kernel_parameters = []
|
||||
|
||||
match self._disk_encryption.encryption_type:
|
||||
case disk.EncryptionType.LvmOnLuks:
|
||||
case EncryptionType.LvmOnLuks:
|
||||
if not lvm.vg_name:
|
||||
raise ValueError(f'Unable to determine VG name for {lvm.name}')
|
||||
|
||||
pv_seg_info = disk.device_handler.lvm_pvseg_info(lvm.vg_name, lvm.name)
|
||||
pv_seg_info = device_handler.lvm_pvseg_info(lvm.vg_name, lvm.name)
|
||||
|
||||
if not pv_seg_info:
|
||||
raise ValueError(f'Unable to determine PV segment info for {lvm.vg_name}/{lvm.name}')
|
||||
|
|
@ -980,7 +998,7 @@ class Installer:
|
|||
else:
|
||||
debug(f'LvmOnLuks, encrypted root partition, identifying by UUID: {uuid}')
|
||||
kernel_parameters.append(f'cryptdevice=UUID={uuid}:cryptlvm root={lvm.safe_dev_path}')
|
||||
case disk.EncryptionType.LuksOnLvm:
|
||||
case EncryptionType.LuksOnLvm:
|
||||
uuid = self._get_luks_uuid_from_mapper_dev(lvm.mapper_path)
|
||||
|
||||
if self._disk_encryption.hsm_device:
|
||||
|
|
@ -989,7 +1007,7 @@ class Installer:
|
|||
else:
|
||||
debug(f'LuksOnLvm, encrypted root partition, identifying by UUID: {uuid}')
|
||||
kernel_parameters.append(f'cryptdevice=UUID={uuid}:root root=/dev/mapper/root')
|
||||
case disk.EncryptionType.NoEncryption:
|
||||
case EncryptionType.NoEncryption:
|
||||
debug(f'Identifying root lvm by mapper device: {lvm.dev_path}')
|
||||
kernel_parameters.append(f'root={lvm.safe_dev_path}')
|
||||
|
||||
|
|
@ -997,13 +1015,13 @@ class Installer:
|
|||
|
||||
def _get_kernel_params(
|
||||
self,
|
||||
root: disk.PartitionModification | disk.LvmVolume,
|
||||
root: PartitionModification | LvmVolume,
|
||||
id_root: bool = True,
|
||||
partuuid: bool = True
|
||||
) -> list[str]:
|
||||
kernel_parameters = []
|
||||
|
||||
if isinstance(root, disk.LvmVolume):
|
||||
if isinstance(root, LvmVolume):
|
||||
kernel_parameters = self._get_kernel_params_lvm(root)
|
||||
else:
|
||||
kernel_parameters = self._get_kernel_params_partition(root, id_root, partuuid)
|
||||
|
|
@ -1030,9 +1048,9 @@ class Installer:
|
|||
|
||||
def _add_systemd_bootloader(
|
||||
self,
|
||||
boot_partition: disk.PartitionModification,
|
||||
root: disk.PartitionModification | disk.LvmVolume,
|
||||
efi_partition: disk.PartitionModification | None,
|
||||
boot_partition: PartitionModification,
|
||||
root: PartitionModification | LvmVolume,
|
||||
efi_partition: PartitionModification | None,
|
||||
uki_enabled: bool = False
|
||||
) -> None:
|
||||
debug('Installing systemd bootloader')
|
||||
|
|
@ -1129,9 +1147,9 @@ class Installer:
|
|||
|
||||
def _add_grub_bootloader(
|
||||
self,
|
||||
boot_partition: disk.PartitionModification,
|
||||
root: disk.PartitionModification | disk.LvmVolume,
|
||||
efi_partition: disk.PartitionModification | None
|
||||
boot_partition: PartitionModification,
|
||||
root: PartitionModification | LvmVolume,
|
||||
efi_partition: PartitionModification | None
|
||||
) -> None:
|
||||
debug('Installing grub bootloader')
|
||||
|
||||
|
|
@ -1189,7 +1207,7 @@ class Installer:
|
|||
else:
|
||||
info(f"GRUB boot partition: {boot_partition.dev_path}")
|
||||
|
||||
parent_dev_path = disk.device_handler.get_parent_device_path(boot_partition.safe_dev_path)
|
||||
parent_dev_path = device_handler.get_parent_device_path(boot_partition.safe_dev_path)
|
||||
|
||||
add_options = [
|
||||
'--target=i386-pc',
|
||||
|
|
@ -1214,9 +1232,9 @@ class Installer:
|
|||
|
||||
def _add_limine_bootloader(
|
||||
self,
|
||||
boot_partition: disk.PartitionModification,
|
||||
efi_partition: disk.PartitionModification | None,
|
||||
root: disk.PartitionModification | disk.LvmVolume
|
||||
boot_partition: PartitionModification,
|
||||
efi_partition: PartitionModification | None,
|
||||
root: PartitionModification | LvmVolume
|
||||
) -> None:
|
||||
debug('Installing limine bootloader')
|
||||
|
||||
|
|
@ -1249,16 +1267,16 @@ class Installer:
|
|||
f' && /usr/bin/cp /usr/share/limine/BOOTX64.EFI {efi_partition.mountpoint}/EFI/BOOT/'
|
||||
)
|
||||
else:
|
||||
parent_dev_path = disk.device_handler.get_parent_device_path(boot_partition.safe_dev_path)
|
||||
parent_dev_path = device_handler.get_parent_device_path(boot_partition.safe_dev_path)
|
||||
|
||||
if unique_path := disk.device_handler.get_unique_path_for_device(parent_dev_path):
|
||||
if unique_path := device_handler.get_unique_path_for_device(parent_dev_path):
|
||||
parent_dev_path = unique_path
|
||||
|
||||
try:
|
||||
# The `limine-bios.sys` file contains stage 3 code.
|
||||
shutil.copy(limine_path / 'limine-bios.sys', self.target / 'boot')
|
||||
|
||||
# `limine bios-install` deploys the stage 1 and 2 to the disk.
|
||||
# `limine bios-install` deploys the stage 1 and 2 to the
|
||||
SysCommand(f'arch-chroot {self.target} limine bios-install {parent_dev_path}', peek_output=True)
|
||||
except Exception as err:
|
||||
raise DiskError(f'Failed to install Limine on {parent_dev_path}: {err}')
|
||||
|
|
@ -1308,8 +1326,8 @@ Exec = /bin/sh -c "{hook_command}"
|
|||
|
||||
def _add_efistub_bootloader(
|
||||
self,
|
||||
boot_partition: disk.PartitionModification,
|
||||
root: disk.PartitionModification | disk.LvmVolume,
|
||||
boot_partition: PartitionModification,
|
||||
root: PartitionModification | LvmVolume,
|
||||
uki_enabled: bool = False
|
||||
) -> None:
|
||||
debug('Installing efistub bootloader')
|
||||
|
|
@ -1336,7 +1354,7 @@ Exec = /bin/sh -c "{hook_command}"
|
|||
loader = '/EFI/Linux/arch-{kernel}.efi'
|
||||
cmdline = []
|
||||
|
||||
parent_dev_path = disk.device_handler.get_parent_device_path(boot_partition.safe_dev_path)
|
||||
parent_dev_path = device_handler.get_parent_device_path(boot_partition.safe_dev_path)
|
||||
|
||||
cmd_template = (
|
||||
'efibootmgr',
|
||||
|
|
@ -1358,8 +1376,8 @@ Exec = /bin/sh -c "{hook_command}"
|
|||
|
||||
def _config_uki(
|
||||
self,
|
||||
root: disk.PartitionModification | disk.LvmVolume,
|
||||
efi_partition: disk.PartitionModification | None
|
||||
root: PartitionModification | LvmVolume,
|
||||
efi_partition: PartitionModification | None
|
||||
) -> None:
|
||||
if not efi_partition or not efi_partition.mountpoint:
|
||||
raise ValueError(f'Could not detect ESP at mountpoint {self.target}')
|
||||
|
|
|
|||
|
|
@ -1,16 +1,37 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.args import arch_config_handler
|
||||
from archinstall.lib.disk.device_handler import device_handler
|
||||
from archinstall.lib.disk.partitioning_menu import manual_partitioning
|
||||
from archinstall.lib.menu.menu_helper import MenuHelper
|
||||
from archinstall.lib.models.device_model import (
|
||||
BDevice,
|
||||
BtrfsMountOption,
|
||||
DeviceModification,
|
||||
DiskLayoutConfiguration,
|
||||
DiskLayoutType,
|
||||
FilesystemType,
|
||||
LvmConfiguration,
|
||||
LvmLayoutType,
|
||||
LvmVolume,
|
||||
LvmVolumeGroup,
|
||||
LvmVolumeStatus,
|
||||
ModificationStatus,
|
||||
PartitionFlag,
|
||||
PartitionModification,
|
||||
PartitionType,
|
||||
SectorSize,
|
||||
Size,
|
||||
SubvolumeModification,
|
||||
Unit,
|
||||
_DeviceInfo,
|
||||
)
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.tui import Alignment, FrameProperties, MenuItem, MenuItemGroup, Orientation, PreviewStyle, ResultType, SelectMenu
|
||||
|
||||
from .. import disk
|
||||
from ..disk.device_model import BtrfsMountOption
|
||||
from ..hardware import SysInfo
|
||||
from ..output import FormattedOutput, debug
|
||||
from ..storage import storage
|
||||
from ..output import FormattedOutput
|
||||
from ..utils.util import prompt_dir
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -21,10 +42,10 @@ if TYPE_CHECKING:
|
|||
_: Callable[[str], DeferredTranslation]
|
||||
|
||||
|
||||
def select_devices(preset: list[disk.BDevice] | None = []) -> list[disk.BDevice]:
|
||||
def select_devices(preset: list[BDevice] | None = []) -> list[BDevice]:
|
||||
def _preview_device_selection(item: MenuItem) -> str | None:
|
||||
device: disk._DeviceInfo = item.get_value()
|
||||
dev = disk.device_handler.get_device(device.path)
|
||||
device: _DeviceInfo = item.get_value()
|
||||
dev = device_handler.get_device(device.path)
|
||||
|
||||
if dev and dev.partition_infos:
|
||||
return FormattedOutput.as_table(dev.partition_infos)
|
||||
|
|
@ -33,7 +54,7 @@ def select_devices(preset: list[disk.BDevice] | None = []) -> list[disk.BDevice]
|
|||
if preset is None:
|
||||
preset = []
|
||||
|
||||
devices = disk.device_handler.devices
|
||||
devices = device_handler.devices
|
||||
options = [d.device_info for d in devices]
|
||||
presets = [p.device_info for p in preset]
|
||||
|
||||
|
|
@ -59,7 +80,7 @@ def select_devices(preset: list[disk.BDevice] | None = []) -> list[disk.BDevice]
|
|||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
selected_device_info: list[disk._DeviceInfo] = result.get_values()
|
||||
selected_device_info: list[_DeviceInfo] = result.get_values()
|
||||
selected_devices = []
|
||||
|
||||
for device in devices:
|
||||
|
|
@ -70,49 +91,43 @@ def select_devices(preset: list[disk.BDevice] | None = []) -> list[disk.BDevice]
|
|||
|
||||
|
||||
def get_default_partition_layout(
|
||||
devices: list[disk.BDevice],
|
||||
filesystem_type: disk.FilesystemType | None = None,
|
||||
advanced_option: bool = False
|
||||
) -> list[disk.DeviceModification]:
|
||||
devices: list[BDevice],
|
||||
filesystem_type: FilesystemType | None = None
|
||||
) -> list[DeviceModification]:
|
||||
if len(devices) == 1:
|
||||
device_modification = suggest_single_disk_layout(
|
||||
devices[0],
|
||||
filesystem_type=filesystem_type,
|
||||
advanced_options=advanced_option
|
||||
filesystem_type=filesystem_type
|
||||
)
|
||||
return [device_modification]
|
||||
else:
|
||||
return suggest_multi_disk_layout(
|
||||
devices,
|
||||
filesystem_type=filesystem_type,
|
||||
advanced_options=advanced_option
|
||||
filesystem_type=filesystem_type
|
||||
)
|
||||
|
||||
|
||||
def _manual_partitioning(
|
||||
preset: list[disk.DeviceModification],
|
||||
devices: list[disk.BDevice]
|
||||
) -> list[disk.DeviceModification]:
|
||||
preset: list[DeviceModification],
|
||||
devices: list[BDevice]
|
||||
) -> list[DeviceModification]:
|
||||
modifications = []
|
||||
for device in devices:
|
||||
mod = next(filter(lambda x: x.device == device, preset), None)
|
||||
if not mod:
|
||||
mod = disk.DeviceModification(device, wipe=False)
|
||||
mod = DeviceModification(device, wipe=False)
|
||||
|
||||
if partitions := disk.manual_partitioning(device, preset=mod.partitions):
|
||||
if partitions := manual_partitioning(device, preset=mod.partitions):
|
||||
mod.partitions = partitions
|
||||
modifications.append(mod)
|
||||
|
||||
return modifications
|
||||
|
||||
|
||||
def select_disk_config(
|
||||
preset: disk.DiskLayoutConfiguration | None = None,
|
||||
advanced_option: bool = False
|
||||
) -> disk.DiskLayoutConfiguration | None:
|
||||
default_layout = disk.DiskLayoutType.Default.display_msg()
|
||||
manual_mode = disk.DiskLayoutType.Manual.display_msg()
|
||||
pre_mount_mode = disk.DiskLayoutType.Pre_mount.display_msg()
|
||||
def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLayoutConfiguration | None:
|
||||
default_layout = DiskLayoutType.Default.display_msg()
|
||||
manual_mode = DiskLayoutType.Manual.display_msg()
|
||||
pre_mount_mode = DiskLayoutType.Pre_mount.display_msg()
|
||||
|
||||
items = [
|
||||
MenuItem(default_layout, value=default_layout),
|
||||
|
|
@ -149,12 +164,10 @@ def select_disk_config(
|
|||
if path is None:
|
||||
return None
|
||||
|
||||
mods = disk.device_handler.detect_pre_mounted_mods(path)
|
||||
mods = device_handler.detect_pre_mounted_mods(path)
|
||||
|
||||
storage['arguments']['mount_point'] = path
|
||||
|
||||
return disk.DiskLayoutConfiguration(
|
||||
config_type=disk.DiskLayoutType.Pre_mount,
|
||||
return DiskLayoutConfiguration(
|
||||
config_type=DiskLayoutType.Pre_mount,
|
||||
device_modifications=mods,
|
||||
mountpoint=path
|
||||
)
|
||||
|
|
@ -166,10 +179,10 @@ def select_disk_config(
|
|||
return None
|
||||
|
||||
if result.get_value() == default_layout:
|
||||
modifications = get_default_partition_layout(devices, advanced_option=advanced_option)
|
||||
modifications = get_default_partition_layout(devices)
|
||||
if modifications:
|
||||
return disk.DiskLayoutConfiguration(
|
||||
config_type=disk.DiskLayoutType.Default,
|
||||
return DiskLayoutConfiguration(
|
||||
config_type=DiskLayoutType.Default,
|
||||
device_modifications=modifications
|
||||
)
|
||||
elif result.get_value() == manual_mode:
|
||||
|
|
@ -177,8 +190,8 @@ def select_disk_config(
|
|||
modifications = _manual_partitioning(preset_mods, devices)
|
||||
|
||||
if modifications:
|
||||
return disk.DiskLayoutConfiguration(
|
||||
config_type=disk.DiskLayoutType.Manual,
|
||||
return DiskLayoutConfiguration(
|
||||
config_type=DiskLayoutType.Manual,
|
||||
device_modifications=modifications
|
||||
)
|
||||
|
||||
|
|
@ -186,11 +199,11 @@ def select_disk_config(
|
|||
|
||||
|
||||
def select_lvm_config(
|
||||
disk_config: disk.DiskLayoutConfiguration,
|
||||
preset: disk.LvmConfiguration | None = None,
|
||||
) -> disk.LvmConfiguration | None:
|
||||
disk_config: DiskLayoutConfiguration,
|
||||
preset: LvmConfiguration | None = None,
|
||||
) -> LvmConfiguration | None:
|
||||
preset_value = preset.config_type.display_msg() if preset else None
|
||||
default_mode = disk.LvmLayoutType.Default.display_msg()
|
||||
default_mode = LvmLayoutType.Default.display_msg()
|
||||
|
||||
items = [MenuItem(default_mode, value=default_mode)]
|
||||
group = MenuItemGroup(items)
|
||||
|
|
@ -216,37 +229,37 @@ def select_lvm_config(
|
|||
return None
|
||||
|
||||
|
||||
def _boot_partition(sector_size: disk.SectorSize, using_gpt: bool) -> disk.PartitionModification:
|
||||
flags = [disk.PartitionFlag.BOOT]
|
||||
size = disk.Size(1, disk.Unit.GiB, sector_size)
|
||||
def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModification:
|
||||
flags = [PartitionFlag.BOOT]
|
||||
size = Size(1, Unit.GiB, sector_size)
|
||||
if using_gpt:
|
||||
start = disk.Size(1, disk.Unit.MiB, sector_size)
|
||||
flags.append(disk.PartitionFlag.ESP)
|
||||
start = Size(1, Unit.MiB, sector_size)
|
||||
flags.append(PartitionFlag.ESP)
|
||||
else:
|
||||
start = disk.Size(3, disk.Unit.MiB, sector_size)
|
||||
start = Size(3, Unit.MiB, sector_size)
|
||||
|
||||
# boot partition
|
||||
return disk.PartitionModification(
|
||||
status=disk.ModificationStatus.Create,
|
||||
type=disk.PartitionType.Primary,
|
||||
return PartitionModification(
|
||||
status=ModificationStatus.Create,
|
||||
type=PartitionType.Primary,
|
||||
start=start,
|
||||
length=size,
|
||||
mountpoint=Path('/boot'),
|
||||
fs_type=disk.FilesystemType.Fat32,
|
||||
fs_type=FilesystemType.Fat32,
|
||||
flags=flags
|
||||
)
|
||||
|
||||
|
||||
def select_main_filesystem_format(advanced_options: bool = False) -> disk.FilesystemType:
|
||||
def select_main_filesystem_format() -> FilesystemType:
|
||||
items = [
|
||||
MenuItem('btrfs', value=disk.FilesystemType.Btrfs),
|
||||
MenuItem('ext4', value=disk.FilesystemType.Ext4),
|
||||
MenuItem('xfs', value=disk.FilesystemType.Xfs),
|
||||
MenuItem('f2fs', value=disk.FilesystemType.F2fs)
|
||||
MenuItem('btrfs', value=FilesystemType.Btrfs),
|
||||
MenuItem('ext4', value=FilesystemType.Ext4),
|
||||
MenuItem('xfs', value=FilesystemType.Xfs),
|
||||
MenuItem('f2fs', value=FilesystemType.F2fs)
|
||||
]
|
||||
|
||||
if advanced_options:
|
||||
items.append(MenuItem('ntfs', value=disk.FilesystemType.Ntfs))
|
||||
if arch_config_handler.args.advanced:
|
||||
items.append(MenuItem('ntfs', value=FilesystemType.Ntfs))
|
||||
|
||||
group = MenuItemGroup(items, sort_items=False)
|
||||
result = SelectMenu(
|
||||
|
|
@ -292,36 +305,35 @@ def select_mount_options() -> list[str]:
|
|||
raise ValueError('Unhandled result type')
|
||||
|
||||
|
||||
def process_root_partition_size(total_size: disk.Size, sector_size: disk.SectorSize) -> disk.Size:
|
||||
def process_root_partition_size(total_size: Size, sector_size: SectorSize) -> Size:
|
||||
# root partition size processing
|
||||
total_device_size = total_size.convert(disk.Unit.GiB)
|
||||
total_device_size = total_size.convert(Unit.GiB)
|
||||
if total_device_size.value > 500:
|
||||
# maximum size
|
||||
return disk.Size(value=50, unit=disk.Unit.GiB, sector_size=sector_size)
|
||||
return Size(value=50, unit=Unit.GiB, sector_size=sector_size)
|
||||
elif total_device_size.value < 320:
|
||||
# minimum size
|
||||
return disk.Size(value=32, unit=disk.Unit.GiB, sector_size=sector_size)
|
||||
return Size(value=32, unit=Unit.GiB, sector_size=sector_size)
|
||||
else:
|
||||
# 10% of total size
|
||||
length = total_device_size.value // 10
|
||||
return disk.Size(value=length, unit=disk.Unit.GiB, sector_size=sector_size)
|
||||
return Size(value=length, unit=Unit.GiB, sector_size=sector_size)
|
||||
|
||||
|
||||
def suggest_single_disk_layout(
|
||||
device: disk.BDevice,
|
||||
filesystem_type: disk.FilesystemType | None = None,
|
||||
advanced_options: bool = False,
|
||||
device: BDevice,
|
||||
filesystem_type: FilesystemType | None = None,
|
||||
separate_home: bool | None = None
|
||||
) -> disk.DeviceModification:
|
||||
) -> DeviceModification:
|
||||
if not filesystem_type:
|
||||
filesystem_type = select_main_filesystem_format(advanced_options)
|
||||
filesystem_type = select_main_filesystem_format()
|
||||
|
||||
sector_size = device.device_info.sector_size
|
||||
total_size = device.device_info.total_size
|
||||
available_space = total_size
|
||||
min_size_to_allow_home_part = disk.Size(64, disk.Unit.GiB, sector_size)
|
||||
min_size_to_allow_home_part = Size(64, Unit.GiB, sector_size)
|
||||
|
||||
if filesystem_type == disk.FilesystemType.Btrfs:
|
||||
if filesystem_type == FilesystemType.Btrfs:
|
||||
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)
|
||||
|
|
@ -340,7 +352,7 @@ def suggest_single_disk_layout(
|
|||
using_subvolumes = False
|
||||
mount_options = []
|
||||
|
||||
device_modification = disk.DeviceModification(device, wipe=True)
|
||||
device_modification = DeviceModification(device, wipe=True)
|
||||
|
||||
using_gpt = SysInfo.has_uefi()
|
||||
|
||||
|
|
@ -395,9 +407,9 @@ def suggest_single_disk_layout(
|
|||
else:
|
||||
root_length = available_space - root_start
|
||||
|
||||
root_partition = disk.PartitionModification(
|
||||
status=disk.ModificationStatus.Create,
|
||||
type=disk.PartitionType.Primary,
|
||||
root_partition = PartitionModification(
|
||||
status=ModificationStatus.Create,
|
||||
type=PartitionType.Primary,
|
||||
start=root_start,
|
||||
length=root_length,
|
||||
mountpoint=Path('/') if not using_subvolumes else None,
|
||||
|
|
@ -412,11 +424,11 @@ 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 = [
|
||||
disk.SubvolumeModification(Path('@'), Path('/')),
|
||||
disk.SubvolumeModification(Path('@home'), Path('/home')),
|
||||
disk.SubvolumeModification(Path('@log'), Path('/var/log')),
|
||||
disk.SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
|
||||
disk.SubvolumeModification(Path('@.snapshots'), Path('/.snapshots'))
|
||||
SubvolumeModification(Path('@'), Path('/')),
|
||||
SubvolumeModification(Path('@home'), Path('/home')),
|
||||
SubvolumeModification(Path('@log'), Path('/var/log')),
|
||||
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
|
||||
SubvolumeModification(Path('@.snapshots'), Path('/.snapshots'))
|
||||
]
|
||||
root_partition.btrfs_subvols = subvolumes
|
||||
elif using_home_partition:
|
||||
|
|
@ -428,11 +440,11 @@ def suggest_single_disk_layout(
|
|||
|
||||
flags = []
|
||||
if using_gpt:
|
||||
flags.append(disk.PartitionFlag.LINUX_HOME)
|
||||
flags.append(PartitionFlag.LINUX_HOME)
|
||||
|
||||
home_partition = disk.PartitionModification(
|
||||
status=disk.ModificationStatus.Create,
|
||||
type=disk.PartitionType.Primary,
|
||||
home_partition = PartitionModification(
|
||||
status=ModificationStatus.Create,
|
||||
type=PartitionType.Primary,
|
||||
start=home_start,
|
||||
length=home_length,
|
||||
mountpoint=Path('/home'),
|
||||
|
|
@ -446,23 +458,22 @@ def suggest_single_disk_layout(
|
|||
|
||||
|
||||
def suggest_multi_disk_layout(
|
||||
devices: list[disk.BDevice],
|
||||
filesystem_type: disk.FilesystemType | None = None,
|
||||
advanced_options: bool = False
|
||||
) -> list[disk.DeviceModification]:
|
||||
devices: list[BDevice],
|
||||
filesystem_type: FilesystemType | None = None
|
||||
) -> list[DeviceModification]:
|
||||
if not devices:
|
||||
return []
|
||||
|
||||
# Not really a rock solid foundation of information to stand on, but it's a start:
|
||||
# https://www.reddit.com/r/btrfs/comments/m287gp/partition_strategy_for_two_physical_disks/
|
||||
# https://www.reddit.com/r/btrfs/comments/9us4hr/what_is_your_btrfs_partitionsubvolumes_scheme/
|
||||
min_home_partition_size = disk.Size(40, disk.Unit.GiB, disk.SectorSize.default())
|
||||
min_home_partition_size = Size(40, Unit.GiB, SectorSize.default())
|
||||
# rough estimate taking in to account user desktops etc. TODO: Catch user packages to detect size?
|
||||
desired_root_partition_size = disk.Size(32, disk.Unit.GiB, disk.SectorSize.default())
|
||||
desired_root_partition_size = Size(32, Unit.GiB, SectorSize.default())
|
||||
mount_options = []
|
||||
|
||||
if not filesystem_type:
|
||||
filesystem_type = select_main_filesystem_format(advanced_options)
|
||||
filesystem_type = select_main_filesystem_format()
|
||||
|
||||
# find proper disk for /home
|
||||
possible_devices = [d for d in devices if d.device_info.total_size >= min_home_partition_size]
|
||||
|
|
@ -475,13 +486,13 @@ def suggest_multi_disk_layout(
|
|||
delta = device.device_info.total_size - desired_root_partition_size
|
||||
devices_delta[device] = delta
|
||||
|
||||
sorted_delta: list[tuple[disk.BDevice, disk.Size]] = sorted(devices_delta.items(), key=lambda x: x[1])
|
||||
root_device: disk.BDevice | None = sorted_delta[0][0]
|
||||
sorted_delta: list[tuple[BDevice, Size]] = sorted(devices_delta.items(), key=lambda x: x[1])
|
||||
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(disk.Unit.GiB)))
|
||||
text += str(_('Minimum capacity for Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(disk.Unit.GiB)))
|
||||
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')))]
|
||||
group = MenuItemGroup(items)
|
||||
|
|
@ -489,7 +500,7 @@ def suggest_multi_disk_layout(
|
|||
|
||||
return []
|
||||
|
||||
if filesystem_type == disk.FilesystemType.Btrfs:
|
||||
if filesystem_type == FilesystemType.Btrfs:
|
||||
mount_options = select_mount_options()
|
||||
|
||||
device_paths = ', '.join([str(d.device_info.path) for d in devices])
|
||||
|
|
@ -498,8 +509,8 @@ def suggest_multi_disk_layout(
|
|||
debug(f'/root: {root_device.device_info.path}')
|
||||
debug(f'/home: {home_device.device_info.path}')
|
||||
|
||||
root_device_modification = disk.DeviceModification(root_device, wipe=True)
|
||||
home_device_modification = disk.DeviceModification(home_device, wipe=True)
|
||||
root_device_modification = DeviceModification(root_device, wipe=True)
|
||||
home_device_modification = DeviceModification(home_device, wipe=True)
|
||||
|
||||
root_device_sector_size = root_device_modification.device.device_info.sector_size
|
||||
home_device_sector_size = home_device_modification.device.device_info.sector_size
|
||||
|
|
@ -519,9 +530,9 @@ def suggest_multi_disk_layout(
|
|||
root_length = root_length.align()
|
||||
|
||||
# add root partition to the root device
|
||||
root_partition = disk.PartitionModification(
|
||||
status=disk.ModificationStatus.Create,
|
||||
type=disk.PartitionType.Primary,
|
||||
root_partition = PartitionModification(
|
||||
status=ModificationStatus.Create,
|
||||
type=PartitionType.Primary,
|
||||
start=root_start,
|
||||
length=root_length,
|
||||
mountpoint=Path('/'),
|
||||
|
|
@ -530,20 +541,20 @@ def suggest_multi_disk_layout(
|
|||
)
|
||||
root_device_modification.add_partition(root_partition)
|
||||
|
||||
home_start = disk.Size(1, disk.Unit.MiB, home_device_sector_size)
|
||||
home_start = Size(1, Unit.MiB, home_device_sector_size)
|
||||
home_length = home_device.device_info.total_size - home_start
|
||||
|
||||
flags = []
|
||||
if using_gpt:
|
||||
home_length = home_length.gpt_end()
|
||||
flags.append(disk.PartitionFlag.LINUX_HOME)
|
||||
flags.append(PartitionFlag.LINUX_HOME)
|
||||
|
||||
home_length = home_length.align()
|
||||
|
||||
# add home partition to home device
|
||||
home_partition = disk.PartitionModification(
|
||||
status=disk.ModificationStatus.Create,
|
||||
type=disk.PartitionType.Primary,
|
||||
home_partition = PartitionModification(
|
||||
status=ModificationStatus.Create,
|
||||
type=PartitionType.Primary,
|
||||
start=home_start,
|
||||
length=home_length,
|
||||
mountpoint=Path('/home'),
|
||||
|
|
@ -557,11 +568,11 @@ def suggest_multi_disk_layout(
|
|||
|
||||
|
||||
def suggest_lvm_layout(
|
||||
disk_config: disk.DiskLayoutConfiguration,
|
||||
filesystem_type: disk.FilesystemType | None = None,
|
||||
disk_config: DiskLayoutConfiguration,
|
||||
filesystem_type: FilesystemType | None = None,
|
||||
vg_grp_name: str = 'ArchinstallVg',
|
||||
) -> disk.LvmConfiguration:
|
||||
if disk_config.config_type != disk.DiskLayoutType.Default:
|
||||
) -> LvmConfiguration:
|
||||
if disk_config.config_type != DiskLayoutType.Default:
|
||||
raise ValueError('LVM suggested volumes are only available for default partitioning')
|
||||
|
||||
using_subvolumes = False
|
||||
|
|
@ -572,7 +583,7 @@ def suggest_lvm_layout(
|
|||
if not filesystem_type:
|
||||
filesystem_type = select_main_filesystem_format()
|
||||
|
||||
if filesystem_type == disk.FilesystemType.Btrfs:
|
||||
if filesystem_type == FilesystemType.Btrfs:
|
||||
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)
|
||||
|
|
@ -592,17 +603,17 @@ def suggest_lvm_layout(
|
|||
|
||||
if using_subvolumes:
|
||||
btrfs_subvols = [
|
||||
disk.SubvolumeModification(Path('@'), Path('/')),
|
||||
disk.SubvolumeModification(Path('@home'), Path('/home')),
|
||||
disk.SubvolumeModification(Path('@log'), Path('/var/log')),
|
||||
disk.SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
|
||||
disk.SubvolumeModification(Path('@.snapshots'), Path('/.snapshots')),
|
||||
SubvolumeModification(Path('@'), Path('/')),
|
||||
SubvolumeModification(Path('@home'), Path('/home')),
|
||||
SubvolumeModification(Path('@log'), Path('/var/log')),
|
||||
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
|
||||
SubvolumeModification(Path('@.snapshots'), Path('/.snapshots')),
|
||||
]
|
||||
|
||||
home_volume = False
|
||||
|
||||
boot_part: disk.PartitionModification | None = None
|
||||
other_part: list[disk.PartitionModification] = []
|
||||
boot_part: PartitionModification | None = None
|
||||
other_part: list[PartitionModification] = []
|
||||
|
||||
for mod in disk_config.device_modifications:
|
||||
for part in mod.partitions:
|
||||
|
|
@ -616,15 +627,15 @@ def suggest_lvm_layout(
|
|||
|
||||
total_vol_available = sum(
|
||||
[p.length for p in other_part],
|
||||
disk.Size(0, disk.Unit.B, disk.SectorSize.default()),
|
||||
Size(0, Unit.B, SectorSize.default()),
|
||||
)
|
||||
root_vol_size = disk.Size(20, disk.Unit.GiB, disk.SectorSize.default())
|
||||
root_vol_size = Size(20, Unit.GiB, SectorSize.default())
|
||||
home_vol_size = total_vol_available - root_vol_size
|
||||
|
||||
lvm_vol_group = disk.LvmVolumeGroup(vg_grp_name, pvs=other_part)
|
||||
lvm_vol_group = LvmVolumeGroup(vg_grp_name, pvs=other_part)
|
||||
|
||||
root_vol = disk.LvmVolume(
|
||||
status=disk.LvmVolumeStatus.Create,
|
||||
root_vol = LvmVolume(
|
||||
status=LvmVolumeStatus.Create,
|
||||
name='root',
|
||||
fs_type=filesystem_type,
|
||||
length=root_vol_size,
|
||||
|
|
@ -636,8 +647,8 @@ def suggest_lvm_layout(
|
|||
lvm_vol_group.volumes.append(root_vol)
|
||||
|
||||
if home_volume:
|
||||
home_vol = disk.LvmVolume(
|
||||
status=disk.LvmVolumeStatus.Create,
|
||||
home_vol = LvmVolume(
|
||||
status=LvmVolumeStatus.Create,
|
||||
name='home',
|
||||
fs_type=filesystem_type,
|
||||
length=home_vol_size,
|
||||
|
|
@ -646,4 +657,4 @@ def suggest_lvm_layout(
|
|||
|
||||
lvm_vol_group.volumes.append(home_vol)
|
||||
|
||||
return disk.LvmConfiguration(disk.LvmLayoutType.Default, [lvm_vol_group])
|
||||
return LvmConfiguration(LvmLayoutType.Default, [lvm_vol_group])
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from archinstall.tui import Alignment, EditMenu, FrameProperties, MenuItem, MenuItemGroup, Orientation, ResultType, SelectMenu, Tui
|
||||
|
||||
from ..args import arch_config_handler
|
||||
from ..locale import list_timezones
|
||||
from ..models.audio_configuration import Audio, AudioConfiguration
|
||||
from ..output import warn
|
||||
from ..packages.packages import validate_package_list
|
||||
from ..storage import storage
|
||||
from ..translationhandler import Language
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -175,7 +175,7 @@ def ask_additional_packages_to_install(preset: list[str] = []) -> list[str]:
|
|||
if len(packages) == 0:
|
||||
return None
|
||||
|
||||
if storage['arguments']['offline'] or storage['arguments']['no_pkg_lookups']:
|
||||
if arch_config_handler.args.offline or arch_config_handler.args.no_pkg_lookups:
|
||||
return None
|
||||
|
||||
# Verify packages that were given
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
from .locale_menu import LocaleConfiguration
|
||||
from .utils import (
|
||||
list_keyboard_languages,
|
||||
list_locales,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.tui import Alignment, FrameProperties, MenuItem, MenuItemGroup, ResultType, SelectMenu
|
||||
|
||||
from ..menu import AbstractSubMenu
|
||||
from .utils import get_kb_layout, list_keyboard_languages, list_locales, set_kb_layout
|
||||
from ..models.locale import LocaleConfiguration
|
||||
from .utils import list_keyboard_languages, list_locales, set_kb_layout
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
|
@ -14,66 +14,20 @@ if TYPE_CHECKING:
|
|||
_: Callable[[str], DeferredTranslation]
|
||||
|
||||
|
||||
@dataclass
|
||||
class LocaleConfiguration:
|
||||
kb_layout: str
|
||||
sys_lang: str
|
||||
sys_enc: str
|
||||
|
||||
@staticmethod
|
||||
def default() -> 'LocaleConfiguration':
|
||||
layout = get_kb_layout()
|
||||
if layout == "":
|
||||
return LocaleConfiguration('us', 'en_US', 'UTF-8')
|
||||
return LocaleConfiguration(layout, 'en_US', 'UTF-8')
|
||||
|
||||
def json(self) -> dict[str, str]:
|
||||
return {
|
||||
'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)
|
||||
return output
|
||||
|
||||
@classmethod
|
||||
def _load_config(cls, config: 'LocaleConfiguration', args: dict[str, Any]) -> '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':
|
||||
default = cls.default()
|
||||
|
||||
if 'locale_config' in args:
|
||||
default = cls._load_config(default, args['locale_config'])
|
||||
else:
|
||||
default = cls._load_config(default, args)
|
||||
|
||||
return default
|
||||
|
||||
|
||||
class LocaleMenu(AbstractSubMenu):
|
||||
def __init__(
|
||||
self,
|
||||
locale_conf: LocaleConfiguration
|
||||
):
|
||||
self._locale_conf = locale_conf
|
||||
self._data_store: dict[str, str] = {}
|
||||
menu_optioons = self._define_menu_options()
|
||||
|
||||
self._item_group = MenuItemGroup(menu_optioons, sort_items=False, checkmarks=True)
|
||||
super().__init__(self._item_group, data_store=self._data_store, allow_reset=True)
|
||||
super().__init__(
|
||||
self._item_group,
|
||||
config=self._locale_conf,
|
||||
allow_reset=True
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
|
|
@ -82,44 +36,36 @@ class LocaleMenu(AbstractSubMenu):
|
|||
action=self._select_kb_layout,
|
||||
value=self._locale_conf.kb_layout,
|
||||
preview_action=self._prev_locale,
|
||||
key='keyboard-layout'
|
||||
key='kb_layout'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Locale language')),
|
||||
action=select_locale_lang,
|
||||
value=self._locale_conf.sys_lang,
|
||||
preview_action=self._prev_locale,
|
||||
key='sys-language'
|
||||
key='sys_lang'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Locale encoding')),
|
||||
action=select_locale_enc,
|
||||
value=self._locale_conf.sys_enc,
|
||||
preview_action=self._prev_locale,
|
||||
key='sys-encoding'
|
||||
key='sys_enc'
|
||||
)
|
||||
]
|
||||
|
||||
def _prev_locale(self, item: MenuItem) -> str | None:
|
||||
temp_locale = LocaleConfiguration(
|
||||
self._menu_item_group.find_by_key('keyboard-layout').get_value(),
|
||||
self._menu_item_group.find_by_key('sys-language').get_value(),
|
||||
self._menu_item_group.find_by_key('sys-encoding').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()
|
||||
|
||||
@override
|
||||
def run(self) -> LocaleConfiguration:
|
||||
super().run()
|
||||
|
||||
if not self._data_store:
|
||||
return LocaleConfiguration.default()
|
||||
|
||||
return LocaleConfiguration(
|
||||
self._data_store['keyboard-layout'],
|
||||
self._data_store['sys-language'],
|
||||
self._data_store['sys-encoding']
|
||||
)
|
||||
return self._locale_conf
|
||||
|
||||
def _select_kb_layout(self, preset: str | None) -> str | None:
|
||||
kb_lang = select_kb_layout(preset)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import time
|
|||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from . import disk
|
||||
from archinstall.lib.disk.utils import get_lsblk_info
|
||||
|
||||
from .exceptions import DiskError, SysCallError
|
||||
from .general import SysCommand, SysCommandWorker, generate_password
|
||||
from .output import debug, info
|
||||
|
|
@ -152,18 +153,19 @@ class Luks2:
|
|||
raise DiskError(f'Failed to open luks2 device: {self.luks_dev_path}')
|
||||
|
||||
def lock(self) -> None:
|
||||
disk.device_handler.umount(self.luks_dev_path)
|
||||
from archinstall.lib.disk.device_handler import device_handler
|
||||
device_handler.umount(self.luks_dev_path)
|
||||
|
||||
# Get crypt-information about the device by doing a reverse lookup starting with the partition path
|
||||
# For instance: /dev/sda
|
||||
lsblk_info = disk.get_lsblk_info(self.luks_dev_path)
|
||||
lsblk_info = get_lsblk_info(self.luks_dev_path)
|
||||
|
||||
# For each child (sub-partition/sub-device)
|
||||
for child in lsblk_info.children:
|
||||
# Unmount the child location
|
||||
for mountpoint in child.mountpoints:
|
||||
debug(f'Unmounting {mountpoint}')
|
||||
disk.device_handler.umount(mountpoint, recursive=True)
|
||||
device_handler.umount(mountpoint, recursive=True)
|
||||
|
||||
# And close it if possible.
|
||||
debug(f"Closing crypt device {child.name}")
|
||||
|
|
|
|||
|
|
@ -13,24 +13,27 @@ if TYPE_CHECKING:
|
|||
_: Callable[[str], DeferredTranslation]
|
||||
|
||||
|
||||
CONFIG_KEY = '__config__'
|
||||
|
||||
|
||||
class AbstractMenu:
|
||||
def __init__(
|
||||
self,
|
||||
item_group: MenuItemGroup,
|
||||
data_store: dict[str, Any],
|
||||
config: Any,
|
||||
auto_cursor: bool = True,
|
||||
allow_reset: bool = False,
|
||||
reset_warning: str | None = None
|
||||
):
|
||||
self._menu_item_group = item_group
|
||||
self._data_store = data_store
|
||||
self._config = config
|
||||
self.auto_cursor = auto_cursor
|
||||
self._allow_reset = allow_reset
|
||||
self._reset_warning = reset_warning
|
||||
|
||||
self.is_context_mgr = False
|
||||
|
||||
self._sync_all_from_ds()
|
||||
self._sync_from_config()
|
||||
|
||||
def __enter__(self, *args: Any, **kwargs: Any) -> AbstractMenu:
|
||||
self.is_context_mgr = True
|
||||
|
|
@ -44,43 +47,48 @@ class AbstractMenu:
|
|||
Tui.print("Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues")
|
||||
raise args[1]
|
||||
|
||||
self._sync_all_to_ds()
|
||||
self.sync_all_to_config()
|
||||
|
||||
def _sync_all_from_ds(self) -> None:
|
||||
def _sync_from_config(self) -> None:
|
||||
for item in self._menu_item_group.menu_items:
|
||||
if item.key is not None:
|
||||
if (store_value := self._data_store.get(item.key, None)) is not None:
|
||||
item.value = store_value
|
||||
if item.key is not None and item.key != CONFIG_KEY:
|
||||
config_value = getattr(self._config, item.key)
|
||||
if config_value is not None:
|
||||
item.value = config_value
|
||||
|
||||
def _sync_all_to_ds(self) -> None:
|
||||
def sync_all_to_config(self) -> None:
|
||||
for item in self._menu_item_group.menu_items:
|
||||
if item.key:
|
||||
self._data_store[item.key] = item.value
|
||||
setattr(self._config, item.key, item.value)
|
||||
|
||||
def _sync(self, item: MenuItem) -> None:
|
||||
if not item.key:
|
||||
if not item.key or item.key == CONFIG_KEY:
|
||||
return
|
||||
|
||||
store_value = self._data_store.get(item.key, None)
|
||||
config_value = getattr(self._config, item.key)
|
||||
|
||||
if store_value is not None:
|
||||
item.value = store_value
|
||||
if config_value is not None:
|
||||
item.value = config_value
|
||||
elif item.value is not None:
|
||||
self._data_store[item.key] = item.value
|
||||
setattr(self._config, item.key, item.value)
|
||||
|
||||
def set_enabled(self, key: str, enabled: bool) -> None:
|
||||
if (item := self._menu_item_group.find_by_key(key)) is not None:
|
||||
item.enabled = enabled
|
||||
return None
|
||||
# the __config__ is associated with multiple items
|
||||
found = False
|
||||
for item in self._menu_item_group.items:
|
||||
if item.key == key:
|
||||
item.enabled = enabled
|
||||
found = True
|
||||
|
||||
raise ValueError(f'No selector found: {key}')
|
||||
if not found:
|
||||
raise ValueError(f'No selector found: {key}')
|
||||
|
||||
def disable_all(self) -> None:
|
||||
for item in self._menu_item_group.items:
|
||||
item.enabled = False
|
||||
|
||||
def run(self) -> Any | None:
|
||||
self._sync_all_from_ds()
|
||||
self._sync_from_config()
|
||||
|
||||
while True:
|
||||
result = SelectMenu(
|
||||
|
|
@ -100,10 +108,9 @@ class AbstractMenu:
|
|||
if item.action is None:
|
||||
break
|
||||
case ResultType.Reset:
|
||||
self._data_store = {}
|
||||
return None
|
||||
|
||||
self._sync_all_to_ds()
|
||||
self.sync_all_to_config()
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -111,7 +118,7 @@ class AbstractSubMenu(AbstractMenu):
|
|||
def __init__(
|
||||
self,
|
||||
item_group: MenuItemGroup,
|
||||
data_store: dict[str, Any],
|
||||
config: Any,
|
||||
auto_cursor: bool = True,
|
||||
allow_reset: bool = False
|
||||
):
|
||||
|
|
@ -120,7 +127,7 @@ class AbstractSubMenu(AbstractMenu):
|
|||
|
||||
super().__init__(
|
||||
item_group,
|
||||
data_store=data_store,
|
||||
config=config,
|
||||
auto_cursor=auto_cursor,
|
||||
allow_reset=allow_reset
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.tui import Alignment, EditMenu, FrameProperties, MenuItem, MenuItemGroup, ResultType, SelectMenu
|
||||
|
||||
from .menu import AbstractSubMenu, ListManager
|
||||
from .models.mirrors import MirrorRegion, mirror_list_handler
|
||||
from .models.mirrors import CustomMirror, MirrorConfiguration, MirrorRegion, SignCheck, SignOption, mirror_list_handler
|
||||
from .output import FormattedOutput
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -16,118 +14,6 @@ if TYPE_CHECKING:
|
|||
_: Callable[[str], DeferredTranslation]
|
||||
|
||||
|
||||
class SignCheck(Enum):
|
||||
Never = 'Never'
|
||||
Optional = 'Optional'
|
||||
Required = 'Required'
|
||||
|
||||
|
||||
class SignOption(Enum):
|
||||
TrustedOnly = 'TrustedOnly'
|
||||
TrustAll = 'TrustAll'
|
||||
|
||||
|
||||
@dataclass
|
||||
class CustomMirror:
|
||||
name: str
|
||||
url: str
|
||||
sign_check: SignCheck
|
||||
sign_option: SignOption
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
def json(self) -> dict[str, str]:
|
||||
return {
|
||||
'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['CustomMirror']:
|
||||
configs = []
|
||||
for arg in args:
|
||||
configs.append(
|
||||
CustomMirror(
|
||||
arg['name'],
|
||||
arg['url'],
|
||||
SignCheck(arg['sign_check']),
|
||||
SignOption(arg['sign_option'])
|
||||
)
|
||||
)
|
||||
|
||||
return configs
|
||||
|
||||
|
||||
@dataclass
|
||||
class MirrorConfiguration:
|
||||
mirror_regions: list[MirrorRegion] = field(default_factory=list)
|
||||
custom_mirrors: list[CustomMirror] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def regions(self) -> str:
|
||||
return ', '.join([m.name for m in self.mirror_regions])
|
||||
|
||||
def json(self) -> dict[str, Any]:
|
||||
regions = {}
|
||||
for m in self.mirror_regions:
|
||||
regions.update(m.json())
|
||||
|
||||
return {
|
||||
'mirror_regions': regions,
|
||||
'custom_mirrors': [c.json() for c in self.custom_mirrors]
|
||||
}
|
||||
|
||||
def mirrorlist_config(self, speed_sort: bool = True) -> str:
|
||||
config = ''
|
||||
|
||||
for mirror_region in self.mirror_regions:
|
||||
sorted_stati = mirror_list_handler.get_status_by_region(
|
||||
mirror_region.name,
|
||||
speed_sort=speed_sort
|
||||
)
|
||||
|
||||
config += f'\n\n## {mirror_region.name}\n'
|
||||
|
||||
for status in sorted_stati:
|
||||
config += f'Server = {status.server_url}\n'
|
||||
|
||||
for cm in self.custom_mirrors:
|
||||
config += f'\n\n## {cm.name}\nServer = {cm.url}\n'
|
||||
|
||||
return config
|
||||
|
||||
def pacman_config(self) -> str:
|
||||
config = ''
|
||||
|
||||
for mirror in self.custom_mirrors:
|
||||
config += f'\n\n[{mirror.name}]\n'
|
||||
config += f'SigLevel = {mirror.sign_check.value} {mirror.sign_option.value}\n'
|
||||
config += f'Server = {mirror.url}\n'
|
||||
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def parse_args(cls, args: dict[str, Any]) -> 'MirrorConfiguration':
|
||||
config = MirrorConfiguration()
|
||||
|
||||
if 'mirror_regions' in args:
|
||||
for region, urls in args['mirror_regions'].items():
|
||||
config.mirror_regions.append(MirrorRegion(region, urls))
|
||||
|
||||
if 'custom_mirrors' in args:
|
||||
config.custom_mirrors = CustomMirror.parse_args(args['custom_mirrors'])
|
||||
|
||||
return config
|
||||
|
||||
|
||||
class CustomMirrorList(ListManager):
|
||||
def __init__(self, custom_mirrors: list[CustomMirror]):
|
||||
self._actions = [
|
||||
|
|
@ -260,12 +146,14 @@ class MirrorMenu(AbstractSubMenu):
|
|||
else:
|
||||
self._mirror_config = MirrorConfiguration()
|
||||
|
||||
self._data_store: dict[str, Any] = {}
|
||||
|
||||
menu_optioons = self._define_menu_options()
|
||||
self._item_group = MenuItemGroup(menu_optioons, checkmarks=True)
|
||||
|
||||
super().__init__(self._item_group, data_store=self._data_store, allow_reset=True)
|
||||
super().__init__(
|
||||
self._item_group,
|
||||
config=self._mirror_config,
|
||||
allow_reset=True
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
|
|
@ -310,14 +198,7 @@ class MirrorMenu(AbstractSubMenu):
|
|||
@override
|
||||
def run(self) -> MirrorConfiguration:
|
||||
super().run()
|
||||
|
||||
if not self._data_store:
|
||||
return MirrorConfiguration()
|
||||
|
||||
return MirrorConfiguration(
|
||||
mirror_regions=self._data_store.get('mirror_regions', None),
|
||||
custom_mirrors=self._data_store.get('custom_mirrors', None),
|
||||
)
|
||||
return self._mirror_config
|
||||
|
||||
|
||||
def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,35 @@
|
|||
from .audio_configuration import Audio, AudioConfiguration
|
||||
from .bootloader import Bootloader
|
||||
from .device_model import (
|
||||
BDevice,
|
||||
DeviceGeometry,
|
||||
DeviceModification,
|
||||
DiskEncryption,
|
||||
DiskLayoutConfiguration,
|
||||
DiskLayoutType,
|
||||
EncryptionType,
|
||||
Fido2Device,
|
||||
FilesystemType,
|
||||
LsblkInfo,
|
||||
LvmConfiguration,
|
||||
LvmLayoutType,
|
||||
LvmVolume,
|
||||
LvmVolumeGroup,
|
||||
LvmVolumeStatus,
|
||||
ModificationStatus,
|
||||
PartitionFlag,
|
||||
PartitionModification,
|
||||
PartitionTable,
|
||||
PartitionType,
|
||||
SectorSize,
|
||||
Size,
|
||||
SubvolumeModification,
|
||||
Unit,
|
||||
_DeviceInfo,
|
||||
)
|
||||
from .gen import LocalPackage, PackageSearch, PackageSearchResult
|
||||
from .locale import LocaleConfiguration
|
||||
from .mirrors import CustomMirror, MirrorConfiguration, MirrorRegion
|
||||
from .network_configuration import NetworkConfiguration, Nic, NicType
|
||||
from .profile_model import ProfileConfiguration
|
||||
from .users import PasswordStrength, User
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
from dataclasses import dataclass
|
||||
from enum import StrEnum, auto
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...default_profiles.applications.pipewire import PipewireProfile
|
||||
from ..hardware import SysInfo
|
||||
from ..installer import Installer
|
||||
from ..output import info
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class Audio(StrEnum):
|
||||
NO_AUDIO = 'No audio server'
|
||||
|
|
@ -30,10 +32,12 @@ class AudioConfiguration:
|
|||
|
||||
def install_audio_config(
|
||||
self,
|
||||
installation: Installer
|
||||
installation: 'Installer'
|
||||
) -> None:
|
||||
info(f'Installing audio server: {self.audio.name}')
|
||||
|
||||
from ...default_profiles.applications.pipewire import PipewireProfile
|
||||
|
||||
match self.audio:
|
||||
case Audio.PIPEWIRE:
|
||||
PipewireProfile().install(installation)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,8 @@ import parted
|
|||
from parted import Disk, Geometry, Partition
|
||||
from pydantic import BaseModel, Field, ValidationInfo, field_serializer, field_validator
|
||||
|
||||
from ..exceptions import DiskError, SysCallError
|
||||
from ..general import SysCommand
|
||||
from ..hardware import SysInfo
|
||||
from ..output import debug
|
||||
from ..storage import storage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
|
@ -78,7 +75,7 @@ class DiskLayoutConfiguration:
|
|||
|
||||
@classmethod
|
||||
def parse_arg(cls, disk_config: _DiskLayoutConfigurationSerialization) -> DiskLayoutConfiguration | None:
|
||||
from .device_handler import device_handler
|
||||
from archinstall.lib.disk.device_handler import device_handler
|
||||
|
||||
device_modifications: list[DeviceModification] = []
|
||||
config_type = disk_config.get('config_type', None)
|
||||
|
|
@ -100,8 +97,6 @@ class DiskLayoutConfiguration:
|
|||
mods = device_handler.detect_pre_mounted_mods(path)
|
||||
device_modifications.extend(mods)
|
||||
|
||||
storage['arguments']['mount_point'] = path
|
||||
|
||||
config.mountpoint = path
|
||||
|
||||
return config
|
||||
|
|
@ -1598,96 +1593,3 @@ class LsblkInfo(BaseModel):
|
|||
for name, field in cls.model_fields.items()
|
||||
if name != 'children'
|
||||
]
|
||||
|
||||
|
||||
class LsblkOutput(BaseModel):
|
||||
blockdevices: list[LsblkInfo]
|
||||
|
||||
|
||||
def _fetch_lsblk_info(
|
||||
dev_path: Path | str | None = None,
|
||||
reverse: bool = False,
|
||||
full_dev_path: bool = False
|
||||
) -> LsblkOutput:
|
||||
cmd = ['lsblk', '--json', '--bytes', '--output', ','.join(LsblkInfo.fields())]
|
||||
|
||||
if reverse:
|
||||
cmd.append('--inverse')
|
||||
|
||||
if full_dev_path:
|
||||
cmd.append('--paths')
|
||||
|
||||
if dev_path:
|
||||
cmd.append(str(dev_path))
|
||||
|
||||
try:
|
||||
worker = SysCommand(cmd)
|
||||
except SysCallError as err:
|
||||
# Get the output minus the message/info from lsblk if it returns a non-zero exit code.
|
||||
if err.worker:
|
||||
err_str = err.worker.decode()
|
||||
debug(f'Error calling lsblk: {err_str}')
|
||||
|
||||
if dev_path:
|
||||
raise DiskError(f'Failed to read disk "{dev_path}" with lsblk')
|
||||
|
||||
raise err
|
||||
|
||||
output = worker.output(remove_cr=False)
|
||||
return LsblkOutput.model_validate_json(output)
|
||||
|
||||
|
||||
def get_lsblk_info(
|
||||
dev_path: Path | str,
|
||||
reverse: bool = False,
|
||||
full_dev_path: bool = False
|
||||
) -> LsblkInfo:
|
||||
infos = _fetch_lsblk_info(dev_path, reverse=reverse, full_dev_path=full_dev_path)
|
||||
|
||||
if infos.blockdevices:
|
||||
return infos.blockdevices[0]
|
||||
|
||||
raise DiskError(f'lsblk failed to retrieve information for "{dev_path}"')
|
||||
|
||||
|
||||
def get_all_lsblk_info() -> list[LsblkInfo]:
|
||||
return _fetch_lsblk_info().blockdevices
|
||||
|
||||
|
||||
def get_lsblk_output() -> LsblkOutput:
|
||||
return _fetch_lsblk_info()
|
||||
|
||||
|
||||
def find_lsblk_info(
|
||||
dev_path: Path | str,
|
||||
info: list[LsblkInfo]
|
||||
) -> LsblkInfo | None:
|
||||
if isinstance(dev_path, str):
|
||||
dev_path = Path(dev_path)
|
||||
|
||||
for lsblk_info in info:
|
||||
if lsblk_info.path == dev_path:
|
||||
return lsblk_info
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_lsblk_by_mountpoint(mountpoint: Path, as_prefix: bool = False) -> list[LsblkInfo]:
|
||||
def _check(infos: list[LsblkInfo]) -> list[LsblkInfo]:
|
||||
devices = []
|
||||
for entry in infos:
|
||||
if as_prefix:
|
||||
matches = [m for m in entry.mountpoints if str(m).startswith(str(mountpoint))]
|
||||
if matches:
|
||||
devices += [entry]
|
||||
elif mountpoint in entry.mountpoints:
|
||||
devices += [entry]
|
||||
|
||||
if len(entry.children) > 0:
|
||||
if len(match := _check(entry.children)) > 0:
|
||||
devices += match
|
||||
|
||||
return devices
|
||||
|
||||
all_info = get_all_lsblk_info()
|
||||
return _check(all_info)
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..locale.utils import get_kb_layout
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from archinstall.lib.translationhandler import DeferredTranslation
|
||||
|
||||
_: Callable[[str], DeferredTranslation]
|
||||
|
||||
|
||||
@dataclass
|
||||
class LocaleConfiguration:
|
||||
kb_layout: str
|
||||
sys_lang: str
|
||||
sys_enc: str
|
||||
|
||||
@staticmethod
|
||||
def default() -> 'LocaleConfiguration':
|
||||
layout = get_kb_layout()
|
||||
if layout == "":
|
||||
return LocaleConfiguration('us', 'en_US', 'UTF-8')
|
||||
return LocaleConfiguration(layout, 'en_US', 'UTF-8')
|
||||
|
||||
def json(self) -> dict[str, str]:
|
||||
return {
|
||||
'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)
|
||||
return output
|
||||
|
||||
@classmethod
|
||||
def _load_config(cls, config: 'LocaleConfiguration', args: dict[str, Any]) -> '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':
|
||||
default = cls.default()
|
||||
|
||||
if 'locale_config' in args:
|
||||
default = cls._load_config(default, args['locale_config'])
|
||||
else:
|
||||
default = cls._load_config(default, args)
|
||||
|
||||
return default
|
||||
|
|
@ -4,14 +4,22 @@ import time
|
|||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from pydantic import BaseModel, field_validator, model_validator
|
||||
|
||||
from ..networking import DownloadTimer, fetch_data_from_url, ping
|
||||
from ..output import debug
|
||||
from ..storage import storage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from archinstall.lib.translationhandler import DeferredTranslation
|
||||
|
||||
_: Callable[[str], DeferredTranslation]
|
||||
|
||||
|
||||
class MirrorStatusEntryV3(BaseModel):
|
||||
|
|
@ -171,7 +179,8 @@ class MirrorListHandler:
|
|||
return available_mirrors
|
||||
|
||||
def load_mirrors(self) -> None:
|
||||
if storage['arguments']['offline']:
|
||||
from ..args import arch_config_handler
|
||||
if arch_config_handler.args.offline:
|
||||
self.load_local_mirrors()
|
||||
else:
|
||||
if not self.load_remote_mirrors():
|
||||
|
|
@ -281,3 +290,116 @@ class MirrorListHandler:
|
|||
|
||||
|
||||
mirror_list_handler = MirrorListHandler()
|
||||
|
||||
|
||||
class SignCheck(Enum):
|
||||
Never = 'Never'
|
||||
Optional = 'Optional'
|
||||
Required = 'Required'
|
||||
|
||||
|
||||
class SignOption(Enum):
|
||||
TrustedOnly = 'TrustedOnly'
|
||||
TrustAll = 'TrustAll'
|
||||
|
||||
|
||||
@dataclass
|
||||
class CustomMirror:
|
||||
name: str
|
||||
url: str
|
||||
sign_check: SignCheck
|
||||
sign_option: SignOption
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
def json(self) -> dict[str, str]:
|
||||
return {
|
||||
'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['CustomMirror']:
|
||||
configs = []
|
||||
for arg in args:
|
||||
configs.append(
|
||||
CustomMirror(
|
||||
arg['name'],
|
||||
arg['url'],
|
||||
SignCheck(arg['sign_check']),
|
||||
SignOption(arg['sign_option'])
|
||||
)
|
||||
)
|
||||
|
||||
return configs
|
||||
|
||||
|
||||
@dataclass
|
||||
class MirrorConfiguration:
|
||||
mirror_regions: list[MirrorRegion] = field(default_factory=list)
|
||||
custom_mirrors: list[CustomMirror] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def regions(self) -> str:
|
||||
return ', '.join([m.name for m in self.mirror_regions])
|
||||
|
||||
def json(self) -> dict[str, Any]:
|
||||
regions = {}
|
||||
for m in self.mirror_regions:
|
||||
regions.update(m.json())
|
||||
|
||||
return {
|
||||
'mirror_regions': regions,
|
||||
'custom_mirrors': [c.json() for c in self.custom_mirrors]
|
||||
}
|
||||
|
||||
def mirrorlist_config(self, speed_sort: bool = True) -> str:
|
||||
config = ''
|
||||
|
||||
for mirror_region in self.mirror_regions:
|
||||
sorted_stati = mirror_list_handler.get_status_by_region(
|
||||
mirror_region.name,
|
||||
speed_sort=speed_sort
|
||||
)
|
||||
|
||||
config += f'\n\n## {mirror_region.name}\n'
|
||||
|
||||
for status in sorted_stati:
|
||||
config += f'Server = {status.server_url}\n'
|
||||
|
||||
for cm in self.custom_mirrors:
|
||||
config += f'\n\n## {cm.name}\nServer = {cm.url}\n'
|
||||
|
||||
return config
|
||||
|
||||
def pacman_config(self) -> str:
|
||||
config = ''
|
||||
|
||||
for mirror in self.custom_mirrors:
|
||||
config += f'\n\n[{mirror.name}]\n'
|
||||
config += f'SigLevel = {mirror.sign_check.value} {mirror.sign_option.value}\n'
|
||||
config += f'Server = {mirror.url}\n'
|
||||
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def parse_args(cls, args: dict[str, Any]) -> 'MirrorConfiguration':
|
||||
config = MirrorConfiguration()
|
||||
|
||||
mirror_regions = args.get('mirror_regions', [])
|
||||
if mirror_regions:
|
||||
for region, urls in mirror_regions.items():
|
||||
config.mirror_regions.append(MirrorRegion(region, urls))
|
||||
|
||||
if 'custom_mirrors' in args:
|
||||
config.custom_mirrors = CustomMirror.parse_args(args['custom_mirrors'])
|
||||
|
||||
return config
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from dataclasses import dataclass, field
|
|||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..profile import ProfileConfiguration
|
||||
from ..models.profile_model import ProfileConfiguration
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class ProfileConfiguration:
|
|||
greeter: GreeterType | None = None
|
||||
|
||||
def json(self) -> dict[str, Any]:
|
||||
from .profiles_handler import profile_handler
|
||||
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,
|
||||
|
|
@ -24,8 +24,7 @@ class ProfileConfiguration:
|
|||
|
||||
@classmethod
|
||||
def parse_arg(cls, arg: dict[str, Any]) -> 'ProfileConfiguration':
|
||||
from .profiles_handler import profile_handler
|
||||
|
||||
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)
|
||||
|
|
@ -322,7 +322,7 @@ def log(
|
|||
|
||||
Journald.log(text, level=level)
|
||||
|
||||
if level != logging.DEBUG or storage.get('arguments', {}).get('verbose', False):
|
||||
if level != logging.DEBUG:
|
||||
from archinstall.tui import Tui
|
||||
Tui.print(text)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
from .profile_menu import ProfileMenu, select_greeter, select_profile
|
||||
from .profile_model import ProfileConfiguration
|
||||
from .profiles_handler import profile_handler
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.default_profiles.profile import GreeterType, Profile
|
||||
from archinstall.tui import Alignment, FrameProperties, MenuItem, MenuItemGroup, Orientation, ResultType, SelectMenu
|
||||
|
|
@ -8,7 +8,7 @@ from archinstall.tui import Alignment, FrameProperties, MenuItem, MenuItemGroup,
|
|||
from ..hardware import GfxDriver
|
||||
from ..interactions.system_conf import select_driver
|
||||
from ..menu import AbstractSubMenu
|
||||
from .profile_model import ProfileConfiguration
|
||||
from ..models.profile_model import ProfileConfiguration
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
|
@ -24,40 +24,42 @@ class ProfileMenu(AbstractSubMenu):
|
|||
preset: ProfileConfiguration | None = None
|
||||
):
|
||||
if preset:
|
||||
self._preset = preset
|
||||
self._profile_config = preset
|
||||
else:
|
||||
self._preset = ProfileConfiguration()
|
||||
|
||||
self._data_store: dict[str, Any] = {}
|
||||
self._profile_config = ProfileConfiguration()
|
||||
|
||||
menu_optioons = self._define_menu_options()
|
||||
self._item_group = MenuItemGroup(menu_optioons, checkmarks=True)
|
||||
|
||||
super().__init__(self._item_group, data_store=self._data_store, allow_reset=True)
|
||||
super().__init__(
|
||||
self._item_group,
|
||||
self._profile_config,
|
||||
allow_reset=True
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=str(_('Type')),
|
||||
action=self._select_profile,
|
||||
value=self._preset.profile,
|
||||
value=self._profile_config.profile,
|
||||
preview_action=self._preview_profile,
|
||||
key='profile'
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Graphics driver')),
|
||||
action=self._select_gfx_driver,
|
||||
value=self._preset.gfx_driver if self._preset.profile and self._preset.profile.is_graphic_driver_supported() else None,
|
||||
value=self._profile_config.gfx_driver if self._profile_config.profile and self._profile_config.profile.is_graphic_driver_supported() else None,
|
||||
preview_action=self._prev_gfx,
|
||||
enabled=self._preset.profile.is_graphic_driver_supported() if self._preset.profile else False,
|
||||
enabled=self._profile_config.profile.is_graphic_driver_supported() if self._profile_config.profile else False,
|
||||
dependencies=['profile'],
|
||||
key='gfx_driver',
|
||||
),
|
||||
MenuItem(
|
||||
text=str(_('Greeter')),
|
||||
action=lambda x: select_greeter(preset=x),
|
||||
value=self._preset.greeter if self._preset.profile and self._preset.profile.is_greeter_supported() else None,
|
||||
enabled=self._preset.profile.is_graphic_driver_supported() if self._preset.profile else False,
|
||||
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',
|
||||
|
|
@ -67,15 +69,7 @@ class ProfileMenu(AbstractSubMenu):
|
|||
@override
|
||||
def run(self) -> ProfileConfiguration | None:
|
||||
super().run()
|
||||
|
||||
if self._data_store.get('profile', None):
|
||||
return ProfileConfiguration(
|
||||
self._data_store.get('profile', None),
|
||||
self._data_store.get('gfx_driver', None),
|
||||
self._data_store.get('greeter', None),
|
||||
)
|
||||
|
||||
return None
|
||||
return self._profile_config
|
||||
|
||||
def _select_profile(self, preset: Profile | None) -> Profile | None:
|
||||
profile = select_profile(preset)
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ from typing import TYPE_CHECKING, Any
|
|||
|
||||
from ...default_profiles.profile import GreeterType, Profile
|
||||
from ..hardware import GfxDriver
|
||||
from ..models.profile_model import ProfileConfiguration
|
||||
from ..networking import fetch_data_from_url, list_interfaces
|
||||
from ..output import debug, error, info
|
||||
from .profile_model import ProfileConfiguration
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
from pathlib import Path
|
||||
|
||||
import archinstall
|
||||
from archinstall import SysInfo, debug, info
|
||||
from archinstall.lib import disk, locale
|
||||
from archinstall import SysInfo
|
||||
from archinstall.lib.args import ArchConfig, arch_config_handler
|
||||
from archinstall.lib.configuration import ConfigurationOutput
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.disk.utils import disk_layouts
|
||||
from archinstall.lib.general import run_custom_user_commands
|
||||
from archinstall.lib.global_menu import GlobalMenu
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.installer import Installer, accessibility_tools_in_use
|
||||
from archinstall.lib.interactions.general_conf import ask_chroot
|
||||
from archinstall.lib.models import AudioConfiguration, Bootloader
|
||||
from archinstall.lib.models.device_model import (
|
||||
DiskLayoutConfiguration,
|
||||
DiskLayoutType,
|
||||
EncryptionType,
|
||||
)
|
||||
from archinstall.lib.models.network_configuration import NetworkConfiguration
|
||||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui import Tui
|
||||
|
||||
if archinstall.arguments.get('help'):
|
||||
print("See `man archinstall` for help.")
|
||||
exit(0)
|
||||
|
||||
|
||||
def ask_user_questions() -> None:
|
||||
"""
|
||||
|
|
@ -25,10 +29,10 @@ def ask_user_questions() -> None:
|
|||
"""
|
||||
|
||||
with Tui():
|
||||
global_menu = GlobalMenu(data_store=archinstall.arguments)
|
||||
global_menu = GlobalMenu(arch_config_handler.config)
|
||||
|
||||
if not archinstall.arguments.get('advanced', False):
|
||||
global_menu.set_enabled('parallel downloads', False)
|
||||
if not arch_config_handler.args.advanced:
|
||||
global_menu.set_enabled('parallel_downloads', False)
|
||||
|
||||
global_menu.run()
|
||||
|
||||
|
|
@ -40,111 +44,114 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
formatted and setup prior to entering this function.
|
||||
"""
|
||||
info('Starting installation...')
|
||||
disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config']
|
||||
|
||||
config: ArchConfig = arch_config_handler.config
|
||||
|
||||
if not config.disk_config:
|
||||
error("No disk configuration provided")
|
||||
return
|
||||
|
||||
# Retrieve list of additional repositories and set boolean values appropriately
|
||||
enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', [])
|
||||
enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', [])
|
||||
run_mkinitcpio = not archinstall.arguments.get('uki')
|
||||
locale_config: locale.LocaleConfiguration = archinstall.arguments['locale_config']
|
||||
disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None)
|
||||
disk_config: DiskLayoutConfiguration = config.disk_config
|
||||
enable_testing = 'testing' in config.additional_repositories
|
||||
enable_multilib = 'multilib' in config.additional_repositories
|
||||
run_mkinitcpio = not config.uki
|
||||
locale_config = config.locale_config
|
||||
disk_encryption = config.disk_encryption
|
||||
|
||||
with Installer(
|
||||
mountpoint,
|
||||
disk_config,
|
||||
disk_encryption=disk_encryption,
|
||||
kernels=archinstall.arguments.get('kernels', ['linux'])
|
||||
kernels=config.kernels
|
||||
) as installation:
|
||||
# Mount all the drives to the desired mountpoint
|
||||
if disk_config.config_type != disk.DiskLayoutType.Pre_mount:
|
||||
if disk_config.config_type != DiskLayoutType.Pre_mount:
|
||||
installation.mount_ordered_layout()
|
||||
|
||||
installation.sanity_check()
|
||||
|
||||
if disk_config.config_type != disk.DiskLayoutType.Pre_mount:
|
||||
if disk_encryption and disk_encryption.encryption_type != disk.EncryptionType.NoEncryption:
|
||||
if disk_config.config_type != DiskLayoutType.Pre_mount:
|
||||
if disk_encryption and disk_encryption.encryption_type != EncryptionType.NoEncryption:
|
||||
# generate encryption key files for the mounted luks devices
|
||||
installation.generate_key_files()
|
||||
|
||||
if mirror_config := archinstall.arguments.get('mirror_config', None):
|
||||
if mirror_config := config.mirror_config:
|
||||
installation.set_mirrors(mirror_config, on_target=False)
|
||||
|
||||
installation.minimal_installation(
|
||||
testing=enable_testing,
|
||||
multilib=enable_multilib,
|
||||
mkinitcpio=run_mkinitcpio,
|
||||
hostname=archinstall.arguments.get('hostname'),
|
||||
hostname=arch_config_handler.config.hostname,
|
||||
locale_config=locale_config
|
||||
)
|
||||
|
||||
if mirror_config := archinstall.arguments.get('mirror_config', None):
|
||||
if mirror_config := config.mirror_config:
|
||||
installation.set_mirrors(mirror_config, on_target=True)
|
||||
|
||||
if archinstall.arguments.get('swap'):
|
||||
if config.swap:
|
||||
installation.setup_swap('zram')
|
||||
|
||||
if archinstall.arguments.get("bootloader") == Bootloader.Grub and SysInfo.has_uefi():
|
||||
if config.bootloader == Bootloader.Grub and SysInfo.has_uefi():
|
||||
installation.add_additional_packages("grub")
|
||||
|
||||
installation.add_bootloader(
|
||||
archinstall.arguments["bootloader"],
|
||||
archinstall.arguments.get('uki', False)
|
||||
)
|
||||
installation.add_bootloader(config.bootloader, config.uki)
|
||||
|
||||
# If user selected to copy the current ISO network configuration
|
||||
# Perform a copy of the config
|
||||
network_config: NetworkConfiguration | None = archinstall.arguments.get('network_config', None)
|
||||
network_config: NetworkConfiguration | None = config.network_config
|
||||
|
||||
if network_config:
|
||||
network_config.install_network_config(
|
||||
installation,
|
||||
archinstall.arguments.get('profile_config', None)
|
||||
config.profile_config
|
||||
)
|
||||
|
||||
if users := archinstall.arguments.get('!users', None):
|
||||
if users := config.users:
|
||||
installation.create_users(users)
|
||||
|
||||
audio_config: AudioConfiguration | None = archinstall.arguments.get('audio_config', None)
|
||||
audio_config: AudioConfiguration | None = config.audio_config
|
||||
if audio_config:
|
||||
audio_config.install_audio_config(installation)
|
||||
else:
|
||||
info("No audio server will be installed")
|
||||
|
||||
if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '':
|
||||
installation.add_additional_packages(archinstall.arguments.get('packages', None))
|
||||
if config.packages and config.packages[0] != '':
|
||||
installation.add_additional_packages(config.packages)
|
||||
|
||||
if profile_config := archinstall.arguments.get('profile_config', None):
|
||||
if profile_config := config.profile_config:
|
||||
profile_handler.install_profile_config(installation, profile_config)
|
||||
|
||||
if timezone := archinstall.arguments.get('timezone', None):
|
||||
if timezone := config.timezone:
|
||||
installation.set_timezone(timezone)
|
||||
|
||||
if archinstall.arguments.get('ntp', False):
|
||||
if config.ntp:
|
||||
installation.activate_time_synchronization()
|
||||
|
||||
if archinstall.accessibility_tools_in_use():
|
||||
if accessibility_tools_in_use():
|
||||
installation.enable_espeakup()
|
||||
|
||||
if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw):
|
||||
if (root_pw := config.root_password) and len(root_pw):
|
||||
installation.user_set_pw('root', root_pw)
|
||||
|
||||
if profile_config := archinstall.arguments.get('profile_config', None):
|
||||
if (profile_config := config.profile_config) and profile_config.profile:
|
||||
profile_config.profile.post_install(installation)
|
||||
|
||||
# If the user provided a list of services to be enabled, pass the list to the enable_service function.
|
||||
# Note that while it's called enable_service, it can actually take a list of services and iterate it.
|
||||
if archinstall.arguments.get('services', None):
|
||||
installation.enable_service(archinstall.arguments.get('services', []))
|
||||
if servies := config.services:
|
||||
installation.enable_service(servies)
|
||||
|
||||
# If the user provided custom commands to be run post-installation, execute them now.
|
||||
if archinstall.arguments.get('custom-commands', None):
|
||||
archinstall.run_custom_user_commands(archinstall.arguments['custom-commands'], installation)
|
||||
if cc := config.custom_commands:
|
||||
run_custom_user_commands(cc, installation)
|
||||
|
||||
installation.genfstab()
|
||||
|
||||
info("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation")
|
||||
|
||||
if not archinstall.arguments.get('silent'):
|
||||
if not arch_config_handler.args.silent:
|
||||
with Tui():
|
||||
chroot = ask_chroot()
|
||||
|
||||
|
|
@ -154,33 +161,35 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
debug(f"Disk states after installing:\n{disk.disk_layouts()}")
|
||||
debug(f"Disk states after installing:\n{disk_layouts()}")
|
||||
|
||||
|
||||
def guided() -> None:
|
||||
if not archinstall.arguments.get('silent'):
|
||||
if not arch_config_handler.args.silent:
|
||||
ask_user_questions()
|
||||
|
||||
config = ConfigurationOutput(archinstall.arguments)
|
||||
config = ConfigurationOutput(arch_config_handler.config)
|
||||
config.write_debug()
|
||||
config.save()
|
||||
|
||||
if archinstall.arguments.get('dry_run'):
|
||||
if arch_config_handler.args.dry_run:
|
||||
exit(0)
|
||||
|
||||
if not archinstall.arguments.get('silent'):
|
||||
if not arch_config_handler.args.silent:
|
||||
with Tui():
|
||||
if not config.confirm_config():
|
||||
debug('Installation aborted')
|
||||
guided()
|
||||
|
||||
fs_handler = disk.FilesystemHandler(
|
||||
archinstall.arguments['disk_config'],
|
||||
archinstall.arguments.get('disk_encryption', None)
|
||||
)
|
||||
if arch_config_handler.config.disk_config:
|
||||
fs_handler = FilesystemHandler(
|
||||
arch_config_handler.config.disk_config,
|
||||
arch_config_handler.config.disk_encryption
|
||||
)
|
||||
|
||||
fs_handler.perform_filesystem_operations()
|
||||
perform_installation(archinstall.arguments.get('mount_point', Path('/mnt')))
|
||||
fs_handler.perform_filesystem_operations()
|
||||
|
||||
perform_installation(arch_config_handler.args.mountpoint)
|
||||
|
||||
|
||||
guided()
|
||||
|
|
|
|||
|
|
@ -1,32 +1,38 @@
|
|||
from pathlib import Path
|
||||
|
||||
import archinstall
|
||||
from archinstall import ConfigurationOutput, Installer, debug, info
|
||||
from archinstall.default_profiles.minimal import MinimalProfile
|
||||
from archinstall.lib import disk
|
||||
from archinstall.lib.interactions import select_devices, suggest_single_disk_layout
|
||||
from archinstall.lib.args import ArchConfig, arch_config_handler
|
||||
from archinstall.lib.configuration import ConfigurationOutput
|
||||
from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
|
||||
from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models import Bootloader, User
|
||||
from archinstall.lib.profile import ProfileConfiguration, profile_handler
|
||||
from archinstall.lib.models.device_model import (
|
||||
DiskLayoutConfiguration,
|
||||
)
|
||||
from archinstall.lib.models.network_configuration import NetworkConfiguration
|
||||
from archinstall.lib.models.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.profile import profile_handler
|
||||
from archinstall.tui import Tui
|
||||
|
||||
info("Minimal only supports:")
|
||||
info(" * Being installed to a single disk")
|
||||
|
||||
if archinstall.arguments.get('help', None):
|
||||
info(" - Optional disk encryption via --!encryption-password=<password>")
|
||||
info(" - Optional filesystem type via --filesystem=<fs type>")
|
||||
info(" - Optional systemd network via --network")
|
||||
|
||||
|
||||
def perform_installation(mountpoint: Path) -> None:
|
||||
disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config']
|
||||
disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None)
|
||||
config: ArchConfig = arch_config_handler.config
|
||||
|
||||
if not config.disk_config:
|
||||
error("No disk configuration provided")
|
||||
return
|
||||
|
||||
disk_config: DiskLayoutConfiguration = config.disk_config
|
||||
disk_encryption = config.disk_encryption
|
||||
|
||||
with Installer(
|
||||
mountpoint,
|
||||
disk_config,
|
||||
disk_encryption=disk_encryption,
|
||||
kernels=archinstall.arguments.get('kernels', ['linux'])
|
||||
kernels=config.kernels
|
||||
) as installation:
|
||||
# Strap in the base system, add a boot loader and configure
|
||||
# some other minor details as specified by this profile and user.
|
||||
|
|
@ -34,9 +40,13 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
installation.set_hostname('minimal-arch')
|
||||
installation.add_bootloader(Bootloader.Systemd)
|
||||
|
||||
# Optionally enable networking:
|
||||
if archinstall.arguments.get('network', None):
|
||||
installation.copy_iso_network_config(enable_services=True)
|
||||
network_config: NetworkConfiguration | None = config.network_config
|
||||
|
||||
if network_config:
|
||||
network_config.install_network_config(
|
||||
installation,
|
||||
config.profile_config
|
||||
)
|
||||
|
||||
installation.add_additional_packages(['nano', 'wget', 'git'])
|
||||
|
||||
|
|
@ -53,61 +63,39 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
info(" * devel (password: devel)")
|
||||
|
||||
|
||||
def prompt_disk_layout() -> None:
|
||||
fs_type = None
|
||||
if filesystem := archinstall.arguments.get('filesystem', None):
|
||||
fs_type = disk.FilesystemType(filesystem)
|
||||
|
||||
devices = select_devices()
|
||||
modifications = suggest_single_disk_layout(devices[0], filesystem_type=fs_type)
|
||||
|
||||
archinstall.arguments['disk_config'] = disk.DiskLayoutConfiguration(
|
||||
config_type=disk.DiskLayoutType.Default,
|
||||
device_modifications=[modifications]
|
||||
)
|
||||
|
||||
|
||||
def parse_disk_encryption() -> None:
|
||||
if enc_password := archinstall.arguments.get('!encryption-password', None):
|
||||
modification: list[disk.DeviceModification] = archinstall.arguments['disk_config']
|
||||
partitions: list[disk.PartitionModification] = []
|
||||
|
||||
# encrypt all partitions except the /boot
|
||||
for mod in modification:
|
||||
partitions += [p for p in mod.partitions if p.mountpoint != Path('/boot')]
|
||||
|
||||
archinstall.arguments['disk_encryption'] = disk.DiskEncryption(
|
||||
encryption_type=disk.EncryptionType.Luks,
|
||||
encryption_password=enc_password,
|
||||
partitions=partitions
|
||||
)
|
||||
|
||||
|
||||
def minimal() -> None:
|
||||
def _minimal() -> None:
|
||||
with Tui():
|
||||
prompt_disk_layout()
|
||||
parse_disk_encryption()
|
||||
disk_config = DiskLayoutConfigurationMenu(disk_layout_config=None).run()
|
||||
|
||||
config = ConfigurationOutput(archinstall.arguments)
|
||||
disk_encryption = None
|
||||
if disk_config:
|
||||
disk_encryption = DiskEncryptionMenu(disk_config).run()
|
||||
|
||||
arch_config_handler.config.disk_config = disk_config
|
||||
arch_config_handler.config.disk_encryption = disk_encryption
|
||||
|
||||
config = ConfigurationOutput(arch_config_handler.config)
|
||||
config.write_debug()
|
||||
config.save()
|
||||
|
||||
if archinstall.arguments.get('dry_run'):
|
||||
if arch_config_handler.args.dry_run:
|
||||
exit(0)
|
||||
|
||||
if not archinstall.arguments.get('silent'):
|
||||
if not arch_config_handler.args.silent:
|
||||
with Tui():
|
||||
if not config.confirm_config():
|
||||
debug('Installation aborted')
|
||||
minimal()
|
||||
_minimal()
|
||||
|
||||
fs_handler = disk.FilesystemHandler(
|
||||
archinstall.arguments['disk_config'],
|
||||
archinstall.arguments.get('disk_encryption', None)
|
||||
)
|
||||
if arch_config_handler.config.disk_config:
|
||||
fs_handler = FilesystemHandler(
|
||||
arch_config_handler.config.disk_config,
|
||||
arch_config_handler.config.disk_encryption
|
||||
)
|
||||
|
||||
fs_handler.perform_filesystem_operations()
|
||||
perform_installation(archinstall.arguments.get('mount_point', Path('/mnt')))
|
||||
fs_handler.perform_filesystem_operations()
|
||||
|
||||
perform_installation(arch_config_handler.args.mountpoint)
|
||||
|
||||
|
||||
minimal()
|
||||
_minimal()
|
||||
|
|
|
|||
|
|
@ -1,25 +1,28 @@
|
|||
from pathlib import Path
|
||||
|
||||
import archinstall
|
||||
from archinstall import debug
|
||||
from archinstall.lib import disk
|
||||
from archinstall import debug, error
|
||||
from archinstall.lib.args import ArchConfig, arch_config_handler
|
||||
from archinstall.lib.configuration import ConfigurationOutput
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.disk.utils import disk_layouts
|
||||
from archinstall.lib.global_menu import GlobalMenu
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models.device_model import (
|
||||
DiskLayoutConfiguration,
|
||||
)
|
||||
from archinstall.tui import Tui
|
||||
|
||||
|
||||
def ask_user_questions() -> None:
|
||||
with Tui():
|
||||
global_menu = archinstall.GlobalMenu(data_store=archinstall.arguments)
|
||||
global_menu = GlobalMenu(arch_config_handler.config)
|
||||
global_menu.disable_all()
|
||||
|
||||
global_menu.set_enabled('archinstall-language', True)
|
||||
global_menu.set_enabled('archinstall_language', True)
|
||||
global_menu.set_enabled('disk_config', True)
|
||||
global_menu.set_enabled('disk_encryption', True)
|
||||
global_menu.set_enabled('swap', True)
|
||||
global_menu.set_enabled('save_config', True)
|
||||
global_menu.set_enabled('install', True)
|
||||
global_menu.set_enabled('abort', True)
|
||||
global_menu.set_enabled('__config__', True)
|
||||
|
||||
global_menu.run()
|
||||
|
||||
|
|
@ -30,18 +33,24 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
Only requirement is that the block devices are
|
||||
formatted and setup prior to entering this function.
|
||||
"""
|
||||
disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config']
|
||||
disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None)
|
||||
config: ArchConfig = arch_config_handler.config
|
||||
|
||||
if not config.disk_config:
|
||||
error("No disk configuration provided")
|
||||
return
|
||||
|
||||
disk_config: DiskLayoutConfiguration = config.disk_config
|
||||
disk_encryption = config.disk_encryption
|
||||
|
||||
with Installer(
|
||||
mountpoint,
|
||||
disk_config,
|
||||
disk_encryption=disk_encryption,
|
||||
kernels=archinstall.arguments.get('kernels', ['linux'])
|
||||
kernels=config.kernels
|
||||
) as installation:
|
||||
# Mount all the drives to the desired mountpoint
|
||||
# This *can* be done outside of the installation, but the installer can deal with it.
|
||||
if archinstall.arguments.get('disk_config'):
|
||||
if disk_config:
|
||||
installation.mount_ordered_layout()
|
||||
|
||||
# to generate a fstab directory holder. Avoids an error on exit and at the same time checks the procedure
|
||||
|
|
@ -50,33 +59,35 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
target.parent.mkdir(parents=True)
|
||||
|
||||
# For support reasons, we'll log the disk layout post installation (crash or no crash)
|
||||
debug(f"Disk states after installing:\n{disk.disk_layouts()}")
|
||||
debug(f"Disk states after installing:\n{disk_layouts()}")
|
||||
|
||||
|
||||
def only_hd() -> None:
|
||||
if not archinstall.arguments.get('silent'):
|
||||
def _only_hd() -> None:
|
||||
if not arch_config_handler.args.silent:
|
||||
ask_user_questions()
|
||||
|
||||
config = ConfigurationOutput(archinstall.arguments)
|
||||
config = ConfigurationOutput(arch_config_handler.config)
|
||||
config.write_debug()
|
||||
config.save()
|
||||
|
||||
if archinstall.arguments.get('dry_run'):
|
||||
if arch_config_handler.args.dry_run:
|
||||
exit(0)
|
||||
|
||||
if not archinstall.arguments.get('silent'):
|
||||
if not arch_config_handler.args.silent:
|
||||
with Tui():
|
||||
if not config.confirm_config():
|
||||
debug('Installation aborted')
|
||||
only_hd()
|
||||
_only_hd()
|
||||
|
||||
fs_handler = disk.FilesystemHandler(
|
||||
archinstall.arguments['disk_config'],
|
||||
archinstall.arguments.get('disk_encryption', None)
|
||||
)
|
||||
if arch_config_handler.config.disk_config:
|
||||
fs_handler = FilesystemHandler(
|
||||
arch_config_handler.config.disk_config,
|
||||
arch_config_handler.config.disk_encryption
|
||||
)
|
||||
|
||||
fs_handler.perform_filesystem_operations()
|
||||
perform_installation(archinstall.arguments.get('mount_point', Path('/mnt')))
|
||||
fs_handler.perform_filesystem_operations()
|
||||
|
||||
perform_installation(arch_config_handler.args.mountpoint)
|
||||
|
||||
|
||||
only_hd()
|
||||
_only_hd()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import time
|
||||
|
||||
import archinstall
|
||||
from archinstall import info, profile
|
||||
from archinstall.lib.output import info
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.lib.storage import storage
|
||||
from archinstall.tui import Tui
|
||||
|
||||
for p in profile.profile_handler.get_mac_addr_profiles():
|
||||
for p in profile_handler.get_mac_addr_profiles():
|
||||
# Tailored means it's a match for this machine
|
||||
# based on it's MAC address (or some other criteria
|
||||
# that fits the requirements for this machine specifically).
|
||||
|
|
@ -15,5 +16,5 @@ for p in profile.profile_handler.get_mac_addr_profiles():
|
|||
Tui.print(f'{i}...')
|
||||
time.sleep(1)
|
||||
|
||||
install_session = archinstall.storage['installation_session']
|
||||
install_session = storage['installation_session']
|
||||
p.install(install_session)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
from pathlib import Path
|
||||
|
||||
from archinstall import disk
|
||||
from archinstall.lib.disk.device_handler import device_handler
|
||||
from archinstall.lib.models.device_model import DiskLayoutConfiguration, DiskLayoutType
|
||||
|
||||
root_mount_dir = Path('/mnt/archinstall')
|
||||
|
||||
mods = disk.device_handler.detect_pre_mounted_mods(root_mount_dir)
|
||||
mods = device_handler.detect_pre_mounted_mods(root_mount_dir)
|
||||
|
||||
disk_config = disk.DiskLayoutConfiguration(
|
||||
disk.DiskLayoutType.Pre_mount,
|
||||
disk_config = DiskLayoutConfiguration(
|
||||
DiskLayoutType.Pre_mount,
|
||||
device_modifications=mods,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,39 +1,58 @@
|
|||
from pathlib import Path
|
||||
|
||||
from archinstall import Installer, disk, models, profile
|
||||
from archinstall.default_profiles.minimal import MinimalProfile
|
||||
from archinstall.lib.disk.device_handler import device_handler
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models.device_model import (
|
||||
DeviceModification,
|
||||
DiskEncryption,
|
||||
DiskLayoutConfiguration,
|
||||
DiskLayoutType,
|
||||
EncryptionType,
|
||||
FilesystemType,
|
||||
ModificationStatus,
|
||||
PartitionFlag,
|
||||
PartitionModification,
|
||||
PartitionType,
|
||||
Size,
|
||||
Unit,
|
||||
)
|
||||
from archinstall.lib.models.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.models.users import User
|
||||
from archinstall.lib.profile import profile_handler
|
||||
|
||||
# we're creating a new ext4 filesystem installation
|
||||
fs_type = disk.FilesystemType('ext4')
|
||||
fs_type = FilesystemType('ext4')
|
||||
device_path = Path('/dev/sda')
|
||||
|
||||
# get the physical disk device
|
||||
device = disk.device_handler.get_device(device_path)
|
||||
device = device_handler.get_device(device_path)
|
||||
|
||||
if not device:
|
||||
raise ValueError('No device found for given path')
|
||||
|
||||
# create a new modification for the specific device
|
||||
device_modification = disk.DeviceModification(device, wipe=True)
|
||||
device_modification = DeviceModification(device, wipe=True)
|
||||
|
||||
# create a new boot partition
|
||||
boot_partition = disk.PartitionModification(
|
||||
status=disk.ModificationStatus.Create,
|
||||
type=disk.PartitionType.Primary,
|
||||
start=disk.Size(1, disk.Unit.MiB, device.device_info.sector_size),
|
||||
length=disk.Size(512, disk.Unit.MiB, device.device_info.sector_size),
|
||||
boot_partition = PartitionModification(
|
||||
status=ModificationStatus.Create,
|
||||
type=PartitionType.Primary,
|
||||
start=Size(1, Unit.MiB, device.device_info.sector_size),
|
||||
length=Size(512, Unit.MiB, device.device_info.sector_size),
|
||||
mountpoint=Path('/boot'),
|
||||
fs_type=disk.FilesystemType.Fat32,
|
||||
flags=[disk.PartitionFlag.BOOT]
|
||||
fs_type=FilesystemType.Fat32,
|
||||
flags=[PartitionFlag.BOOT]
|
||||
)
|
||||
device_modification.add_partition(boot_partition)
|
||||
|
||||
# create a root partition
|
||||
root_partition = disk.PartitionModification(
|
||||
status=disk.ModificationStatus.Create,
|
||||
type=disk.PartitionType.Primary,
|
||||
start=disk.Size(513, disk.Unit.MiB, device.device_info.sector_size),
|
||||
length=disk.Size(20, disk.Unit.GiB, device.device_info.sector_size),
|
||||
root_partition = PartitionModification(
|
||||
status=ModificationStatus.Create,
|
||||
type=PartitionType.Primary,
|
||||
start=Size(513, Unit.MiB, device.device_info.sector_size),
|
||||
length=Size(20, Unit.GiB, device.device_info.sector_size),
|
||||
mountpoint=None,
|
||||
fs_type=fs_type,
|
||||
mount_options=[],
|
||||
|
|
@ -44,9 +63,9 @@ start_home = root_partition.length
|
|||
length_home = device.device_info.total_size - start_home
|
||||
|
||||
# create a new home partition
|
||||
home_partition = disk.PartitionModification(
|
||||
status=disk.ModificationStatus.Create,
|
||||
type=disk.PartitionType.Primary,
|
||||
home_partition = PartitionModification(
|
||||
status=ModificationStatus.Create,
|
||||
type=PartitionType.Primary,
|
||||
start=start_home,
|
||||
length=length_home,
|
||||
mountpoint=Path('/home'),
|
||||
|
|
@ -55,21 +74,21 @@ home_partition = disk.PartitionModification(
|
|||
)
|
||||
device_modification.add_partition(home_partition)
|
||||
|
||||
disk_config = disk.DiskLayoutConfiguration(
|
||||
config_type=disk.DiskLayoutType.Default,
|
||||
disk_config = DiskLayoutConfiguration(
|
||||
config_type=DiskLayoutType.Default,
|
||||
device_modifications=[device_modification]
|
||||
)
|
||||
|
||||
# disk encryption configuration (Optional)
|
||||
disk_encryption = disk.DiskEncryption(
|
||||
disk_encryption = DiskEncryption(
|
||||
encryption_password="enc_password",
|
||||
encryption_type=disk.EncryptionType.Luks,
|
||||
encryption_type=EncryptionType.Luks,
|
||||
partitions=[home_partition],
|
||||
hsm_device=None
|
||||
)
|
||||
|
||||
# initiate file handler with the disk config and the optional disk encryption config
|
||||
fs_handler = disk.FilesystemHandler(disk_config, disk_encryption)
|
||||
fs_handler = FilesystemHandler(disk_config, disk_encryption)
|
||||
|
||||
# perform all file operations
|
||||
# WARNING: this will potentially format the filesystem and delete all data
|
||||
|
|
@ -89,8 +108,8 @@ with Installer(
|
|||
|
||||
# Optionally, install a profile of choice.
|
||||
# In this case, we install a minimal profile that is empty
|
||||
profile_config = profile.ProfileConfiguration(MinimalProfile())
|
||||
profile.profile_handler.install_profile_config(installation, profile_config)
|
||||
profile_config = ProfileConfiguration(MinimalProfile())
|
||||
profile_handler.install_profile_config(installation, profile_config)
|
||||
|
||||
user = models.User('archinstall', 'password', True)
|
||||
user = User('archinstall', 'password', True)
|
||||
installation.create_users(user)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
from pathlib import Path
|
||||
|
||||
import archinstall
|
||||
from archinstall import SysInfo, debug, info
|
||||
from archinstall.lib import disk, locale
|
||||
from archinstall import SysInfo, debug, error, info
|
||||
from archinstall.lib.args import ArchConfig, arch_config_handler
|
||||
from archinstall.lib.configuration import ConfigurationOutput
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.disk.utils import disk_layouts
|
||||
from archinstall.lib.general import run_custom_user_commands
|
||||
from archinstall.lib.global_menu import GlobalMenu
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.installer import Installer, accessibility_tools_in_use
|
||||
from archinstall.lib.interactions.general_conf import ask_chroot
|
||||
from archinstall.lib.models import AudioConfiguration, Bootloader
|
||||
from archinstall.lib.models.device_model import (
|
||||
DiskLayoutConfiguration,
|
||||
DiskLayoutType,
|
||||
EncryptionType,
|
||||
)
|
||||
from archinstall.lib.models.network_configuration import NetworkConfiguration
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui import Tui
|
||||
|
||||
if archinstall.arguments.get('help'):
|
||||
print("See `man archinstall` for help.")
|
||||
exit(0)
|
||||
|
||||
|
||||
def ask_user_questions() -> None:
|
||||
"""
|
||||
|
|
@ -25,10 +28,10 @@ def ask_user_questions() -> None:
|
|||
"""
|
||||
|
||||
with Tui():
|
||||
global_menu = GlobalMenu(data_store=archinstall.arguments)
|
||||
global_menu = GlobalMenu(arch_config_handler.config)
|
||||
|
||||
if not archinstall.arguments.get('advanced', False):
|
||||
global_menu.set_enabled('parallel downloads', False)
|
||||
if not arch_config_handler.args.advanced:
|
||||
global_menu.set_enabled('parallel_downloads', False)
|
||||
|
||||
global_menu.run()
|
||||
|
||||
|
|
@ -40,111 +43,114 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
formatted and setup prior to entering this function.
|
||||
"""
|
||||
info('Starting installation...')
|
||||
disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config']
|
||||
|
||||
config: ArchConfig = arch_config_handler.config
|
||||
|
||||
if not config.disk_config:
|
||||
error("No disk configuration provided")
|
||||
return
|
||||
|
||||
# Retrieve list of additional repositories and set boolean values appropriately
|
||||
enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', [])
|
||||
enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', [])
|
||||
run_mkinitcpio = not archinstall.arguments.get('uki')
|
||||
locale_config: locale.LocaleConfiguration = archinstall.arguments['locale_config']
|
||||
disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None)
|
||||
disk_config: DiskLayoutConfiguration = config.disk_config
|
||||
enable_testing = 'testing' in config.additional_repositories
|
||||
enable_multilib = 'multilib' in config.additional_repositories
|
||||
run_mkinitcpio = not config.uki
|
||||
locale_config = config.locale_config
|
||||
disk_encryption = config.disk_encryption
|
||||
|
||||
with Installer(
|
||||
mountpoint,
|
||||
disk_config,
|
||||
disk_encryption=disk_encryption,
|
||||
kernels=archinstall.arguments.get('kernels', ['linux'])
|
||||
kernels=config.kernels
|
||||
) as installation:
|
||||
# Mount all the drives to the desired mountpoint
|
||||
if disk_config.config_type != disk.DiskLayoutType.Pre_mount:
|
||||
if disk_config.config_type != DiskLayoutType.Pre_mount:
|
||||
installation.mount_ordered_layout()
|
||||
|
||||
installation.sanity_check()
|
||||
|
||||
if disk_config.config_type != disk.DiskLayoutType.Pre_mount:
|
||||
if disk_encryption and disk_encryption.encryption_type != disk.EncryptionType.NoEncryption:
|
||||
if disk_config.config_type != DiskLayoutType.Pre_mount:
|
||||
if disk_encryption and disk_encryption.encryption_type != EncryptionType.NoEncryption:
|
||||
# generate encryption key files for the mounted luks devices
|
||||
installation.generate_key_files()
|
||||
|
||||
if mirror_config := archinstall.arguments.get('mirror_config', None):
|
||||
if mirror_config := config.mirror_config:
|
||||
installation.set_mirrors(mirror_config, on_target=False)
|
||||
|
||||
installation.minimal_installation(
|
||||
testing=enable_testing,
|
||||
multilib=enable_multilib,
|
||||
mkinitcpio=run_mkinitcpio,
|
||||
hostname=archinstall.arguments.get('hostname', 'archlinux'),
|
||||
hostname=arch_config_handler.config.hostname,
|
||||
locale_config=locale_config
|
||||
)
|
||||
|
||||
if mirror_config := archinstall.arguments.get('mirror_config', None):
|
||||
if mirror_config := config.mirror_config:
|
||||
installation.set_mirrors(mirror_config, on_target=True)
|
||||
|
||||
if archinstall.arguments.get('swap'):
|
||||
if config.swap:
|
||||
installation.setup_swap('zram')
|
||||
|
||||
if archinstall.arguments.get("bootloader") == Bootloader.Grub and SysInfo.has_uefi():
|
||||
if config.bootloader == Bootloader.Grub and SysInfo.has_uefi():
|
||||
installation.add_additional_packages("grub")
|
||||
|
||||
installation.add_bootloader(
|
||||
archinstall.arguments["bootloader"],
|
||||
archinstall.arguments.get('uki', False)
|
||||
)
|
||||
installation.add_bootloader(config.bootloader, config.uki)
|
||||
|
||||
# If user selected to copy the current ISO network configuration
|
||||
# Perform a copy of the config
|
||||
network_config: NetworkConfiguration | None = archinstall.arguments.get('network_config', None)
|
||||
network_config: NetworkConfiguration | None = config.network_config
|
||||
|
||||
if network_config:
|
||||
network_config.install_network_config(
|
||||
installation,
|
||||
archinstall.arguments.get('profile_config', None)
|
||||
config.profile_config
|
||||
)
|
||||
|
||||
if users := archinstall.arguments.get('!users', None):
|
||||
if users := config.users:
|
||||
installation.create_users(users)
|
||||
|
||||
audio_config: AudioConfiguration | None = archinstall.arguments.get('audio_config', None)
|
||||
audio_config: AudioConfiguration | None = config.audio_config
|
||||
if audio_config:
|
||||
audio_config.install_audio_config(installation)
|
||||
else:
|
||||
info("No audio server will be installed")
|
||||
|
||||
if archinstall.arguments.get('packages', None) and archinstall.arguments.get('packages', None)[0] != '':
|
||||
installation.add_additional_packages(archinstall.arguments.get('packages', None))
|
||||
if config.packages and config.packages[0] != '':
|
||||
installation.add_additional_packages(config.packages)
|
||||
|
||||
if profile_config := archinstall.arguments.get('profile_config', None):
|
||||
if profile_config := config.profile_config:
|
||||
profile_handler.install_profile_config(installation, profile_config)
|
||||
|
||||
if timezone := archinstall.arguments.get('timezone', None):
|
||||
if timezone := config.timezone:
|
||||
installation.set_timezone(timezone)
|
||||
|
||||
if archinstall.arguments.get('ntp', False):
|
||||
if config.ntp:
|
||||
installation.activate_time_synchronization()
|
||||
|
||||
if archinstall.accessibility_tools_in_use():
|
||||
if accessibility_tools_in_use():
|
||||
installation.enable_espeakup()
|
||||
|
||||
if (root_pw := archinstall.arguments.get('!root-password', None)) and len(root_pw):
|
||||
if (root_pw := config.root_password) and len(root_pw):
|
||||
installation.user_set_pw('root', root_pw)
|
||||
|
||||
if profile_config := archinstall.arguments.get('profile_config', None):
|
||||
if (profile_config := config.profile_config) and profile_config.profile:
|
||||
profile_config.profile.post_install(installation)
|
||||
|
||||
# If the user provided a list of services to be enabled, pass the list to the enable_service function.
|
||||
# Note that while it's called enable_service, it can actually take a list of services and iterate it.
|
||||
if archinstall.arguments.get('services', None):
|
||||
installation.enable_service(archinstall.arguments.get('services', []))
|
||||
if servies := config.services:
|
||||
installation.enable_service(servies)
|
||||
|
||||
# If the user provided custom commands to be run post-installation, execute them now.
|
||||
if archinstall.arguments.get('custom-commands', None):
|
||||
archinstall.run_custom_user_commands(archinstall.arguments['custom-commands'], installation)
|
||||
if cc := config.custom_commands:
|
||||
run_custom_user_commands(cc, installation)
|
||||
|
||||
installation.genfstab()
|
||||
|
||||
info("For post-installation tips, see https://wiki.archlinux.org/index.php/Installation_guide#Post-installation")
|
||||
|
||||
if not archinstall.arguments.get('silent'):
|
||||
if not arch_config_handler.args.silent:
|
||||
with Tui():
|
||||
chroot = ask_chroot()
|
||||
|
||||
|
|
@ -154,33 +160,35 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
debug(f"Disk states after installing:\n{disk.disk_layouts()}")
|
||||
debug(f"Disk states after installing:\n{disk_layouts()}")
|
||||
|
||||
|
||||
def _guided() -> None:
|
||||
if not archinstall.arguments.get('silent'):
|
||||
def guided() -> None:
|
||||
if not arch_config_handler.args.silent:
|
||||
ask_user_questions()
|
||||
|
||||
config = ConfigurationOutput(archinstall.arguments)
|
||||
config = ConfigurationOutput(arch_config_handler.config)
|
||||
config.write_debug()
|
||||
config.save()
|
||||
|
||||
if archinstall.arguments.get('dry_run'):
|
||||
if arch_config_handler.args.dry_run:
|
||||
exit(0)
|
||||
|
||||
if not archinstall.arguments.get('silent'):
|
||||
if not arch_config_handler.args.silent:
|
||||
with Tui():
|
||||
if not config.confirm_config():
|
||||
debug('Installation aborted')
|
||||
_guided()
|
||||
guided()
|
||||
|
||||
fs_handler = disk.FilesystemHandler(
|
||||
archinstall.arguments['disk_config'],
|
||||
archinstall.arguments.get('disk_encryption', None)
|
||||
)
|
||||
if arch_config_handler.config.disk_config:
|
||||
fs_handler = FilesystemHandler(
|
||||
arch_config_handler.config.disk_config,
|
||||
arch_config_handler.config.disk_encryption
|
||||
)
|
||||
|
||||
fs_handler.perform_filesystem_operations()
|
||||
perform_installation(archinstall.arguments.get('mount_point', Path('/mnt')))
|
||||
fs_handler.perform_filesystem_operations()
|
||||
|
||||
perform_installation(arch_config_handler.args.mountpoint)
|
||||
|
||||
|
||||
_guided()
|
||||
guided()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import time
|
||||
|
||||
import archinstall
|
||||
from archinstall import info, profile
|
||||
from archinstall.lib.output import info
|
||||
from archinstall.lib.profile import profile_handler
|
||||
from archinstall.lib.storage import storage
|
||||
from archinstall.tui import Tui
|
||||
|
||||
for _profile in profile.profile_handler.get_mac_addr_profiles():
|
||||
for _profile in profile_handler.get_mac_addr_profiles():
|
||||
# Tailored means it's a match for this machine
|
||||
# based on it's MAC address (or some other criteria
|
||||
# that fits the requirements for this machine specifically).
|
||||
|
|
@ -15,5 +16,5 @@ for _profile in profile.profile_handler.get_mac_addr_profiles():
|
|||
Tui.print(f'{i}...')
|
||||
time.sleep(1)
|
||||
|
||||
install_session = archinstall.storage['installation_session']
|
||||
install_session = storage['installation_session']
|
||||
_profile.install(install_session)
|
||||
|
|
|
|||
|
|
@ -1,32 +1,37 @@
|
|||
from pathlib import Path
|
||||
|
||||
import archinstall
|
||||
from archinstall import ConfigurationOutput, Installer, debug, info
|
||||
from archinstall.default_profiles.minimal import MinimalProfile
|
||||
from archinstall.lib import disk
|
||||
from archinstall.lib.interactions import select_devices, suggest_single_disk_layout
|
||||
from archinstall.lib.args import ArchConfig, arch_config_handler
|
||||
from archinstall.lib.configuration import ConfigurationOutput
|
||||
from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
|
||||
from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models import Bootloader, User
|
||||
from archinstall.lib.profile import ProfileConfiguration, profile_handler
|
||||
from archinstall.lib.models.device_model import DiskLayoutConfiguration
|
||||
from archinstall.lib.models.network_configuration import NetworkConfiguration
|
||||
from archinstall.lib.models.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.profile import profile_handler
|
||||
from archinstall.tui import Tui
|
||||
|
||||
info("Minimal only supports:")
|
||||
info(" * Being installed to a single disk")
|
||||
|
||||
if archinstall.arguments.get('help', None):
|
||||
info(" - Optional disk encryption via --!encryption-password=<password>")
|
||||
info(" - Optional filesystem type via --filesystem=<fs type>")
|
||||
info(" - Optional systemd network via --network")
|
||||
|
||||
|
||||
def perform_installation(mountpoint: Path) -> None:
|
||||
disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config']
|
||||
disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None)
|
||||
config: ArchConfig = arch_config_handler.config
|
||||
|
||||
disk_config: DiskLayoutConfiguration | None = config.disk_config
|
||||
|
||||
if disk_config is None:
|
||||
error("No disk configuration provided")
|
||||
return
|
||||
|
||||
disk_encryption = config.disk_encryption
|
||||
|
||||
with Installer(
|
||||
mountpoint,
|
||||
disk_config,
|
||||
disk_encryption=disk_encryption,
|
||||
kernels=archinstall.arguments.get('kernels', ['linux'])
|
||||
kernels=config.kernels
|
||||
) as installation:
|
||||
# Strap in the base system, add a boot loader and configure
|
||||
# some other minor details as specified by this profile and user.
|
||||
|
|
@ -34,9 +39,13 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
installation.set_hostname('minimal-arch')
|
||||
installation.add_bootloader(Bootloader.Systemd)
|
||||
|
||||
# Optionally enable networking:
|
||||
if archinstall.arguments.get('network', None):
|
||||
installation.copy_iso_network_config(enable_services=True)
|
||||
network_config: NetworkConfiguration | None = config.network_config
|
||||
|
||||
if network_config:
|
||||
network_config.install_network_config(
|
||||
installation,
|
||||
config.profile_config
|
||||
)
|
||||
|
||||
installation.add_additional_packages(['nano', 'wget', 'git'])
|
||||
|
||||
|
|
@ -53,61 +62,39 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
info(" * devel (password: devel)")
|
||||
|
||||
|
||||
def prompt_disk_layout() -> None:
|
||||
fs_type = None
|
||||
if filesystem := archinstall.arguments.get('filesystem', None):
|
||||
fs_type = disk.FilesystemType(filesystem)
|
||||
|
||||
devices = select_devices()
|
||||
modifications = suggest_single_disk_layout(devices[0], filesystem_type=fs_type)
|
||||
|
||||
archinstall.arguments['disk_config'] = disk.DiskLayoutConfiguration(
|
||||
config_type=disk.DiskLayoutType.Default,
|
||||
device_modifications=[modifications]
|
||||
)
|
||||
|
||||
|
||||
def parse_disk_encryption() -> None:
|
||||
if enc_password := archinstall.arguments.get('!encryption-password', None):
|
||||
modification: list[disk.DeviceModification] = archinstall.arguments['disk_config']
|
||||
partitions: list[disk.PartitionModification] = []
|
||||
|
||||
# encrypt all partitions except the /boot
|
||||
for mod in modification:
|
||||
partitions += [p for p in mod.partitions if p.mountpoint != Path('/boot')]
|
||||
|
||||
archinstall.arguments['disk_encryption'] = disk.DiskEncryption(
|
||||
encryption_type=disk.EncryptionType.Luks,
|
||||
encryption_password=enc_password,
|
||||
partitions=partitions
|
||||
)
|
||||
|
||||
|
||||
def _minimal() -> None:
|
||||
with Tui():
|
||||
prompt_disk_layout()
|
||||
parse_disk_encryption()
|
||||
disk_config = DiskLayoutConfigurationMenu(disk_layout_config=None).run()
|
||||
|
||||
config = ConfigurationOutput(archinstall.arguments)
|
||||
disk_encryption = None
|
||||
if disk_config:
|
||||
disk_encryption = DiskEncryptionMenu(disk_config).run()
|
||||
|
||||
arch_config_handler.config.disk_config = disk_config
|
||||
arch_config_handler.config.disk_encryption = disk_encryption
|
||||
|
||||
config = ConfigurationOutput(arch_config_handler.config)
|
||||
config.write_debug()
|
||||
config.save()
|
||||
|
||||
if archinstall.arguments.get('dry_run'):
|
||||
if arch_config_handler.args.dry_run:
|
||||
exit(0)
|
||||
|
||||
if not archinstall.arguments.get('silent'):
|
||||
if not arch_config_handler.args.silent:
|
||||
with Tui():
|
||||
if not config.confirm_config():
|
||||
debug('Installation aborted')
|
||||
_minimal()
|
||||
|
||||
fs_handler = disk.FilesystemHandler(
|
||||
archinstall.arguments['disk_config'],
|
||||
archinstall.arguments.get('disk_encryption', None)
|
||||
)
|
||||
if arch_config_handler.config.disk_config:
|
||||
fs_handler = FilesystemHandler(
|
||||
arch_config_handler.config.disk_config,
|
||||
arch_config_handler.config.disk_encryption
|
||||
)
|
||||
|
||||
fs_handler.perform_filesystem_operations()
|
||||
perform_installation(archinstall.arguments.get('mount_point', Path('/mnt')))
|
||||
fs_handler.perform_filesystem_operations()
|
||||
|
||||
perform_installation(arch_config_handler.args.mountpoint)
|
||||
|
||||
|
||||
_minimal()
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
from pathlib import Path
|
||||
|
||||
import archinstall
|
||||
from archinstall import debug
|
||||
from archinstall.lib import disk
|
||||
from archinstall.lib.args import ArchConfig, arch_config_handler
|
||||
from archinstall.lib.configuration import ConfigurationOutput
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.disk.utils import disk_layouts
|
||||
from archinstall.lib.global_menu import GlobalMenu
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models.device_model import DiskLayoutConfiguration
|
||||
from archinstall.lib.output import debug, error
|
||||
from archinstall.tui import Tui
|
||||
|
||||
|
||||
def ask_user_questions() -> None:
|
||||
with Tui():
|
||||
global_menu = archinstall.GlobalMenu(data_store=archinstall.arguments)
|
||||
global_menu = GlobalMenu(arch_config_handler.config)
|
||||
global_menu.disable_all()
|
||||
|
||||
global_menu.set_enabled('archinstall-language', True)
|
||||
global_menu.set_enabled('disk_config', True)
|
||||
global_menu.set_enabled('disk_encryption', True)
|
||||
global_menu.set_enabled('_disk_config', True)
|
||||
global_menu.set_enabled('_disk_encryption', True)
|
||||
global_menu.set_enabled('swap', True)
|
||||
global_menu.set_enabled('save_config', True)
|
||||
global_menu.set_enabled('install', True)
|
||||
|
|
@ -30,18 +33,24 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
Only requirement is that the block devices are
|
||||
formatted and setup prior to entering this function.
|
||||
"""
|
||||
disk_config: disk.DiskLayoutConfiguration = archinstall.arguments['disk_config']
|
||||
disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None)
|
||||
config: ArchConfig = arch_config_handler.config
|
||||
|
||||
if not config.disk_config:
|
||||
error("No disk configuration provided")
|
||||
return
|
||||
|
||||
disk_config: DiskLayoutConfiguration = config.disk_config
|
||||
disk_encryption = config.disk_encryption
|
||||
|
||||
with Installer(
|
||||
mountpoint,
|
||||
disk_config,
|
||||
disk_encryption=disk_encryption,
|
||||
kernels=archinstall.arguments.get('kernels', ['linux'])
|
||||
kernels=config.kernels
|
||||
) as installation:
|
||||
# Mount all the drives to the desired mountpoint
|
||||
# This *can* be done outside of the installation, but the installer can deal with it.
|
||||
if archinstall.arguments.get('disk_config'):
|
||||
if disk_config:
|
||||
installation.mount_ordered_layout()
|
||||
|
||||
# to generate a fstab directory holder. Avoids an error on exit and at the same time checks the procedure
|
||||
|
|
@ -50,33 +59,35 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
target.parent.mkdir(parents=True)
|
||||
|
||||
# For support reasons, we'll log the disk layout post installation (crash or no crash)
|
||||
debug(f"Disk states after installing:\n{disk.disk_layouts()}")
|
||||
debug(f"Disk states after installing:\n{disk_layouts()}")
|
||||
|
||||
|
||||
def _only_hd() -> None:
|
||||
if not archinstall.arguments.get('silent'):
|
||||
if not arch_config_handler.args.silent:
|
||||
ask_user_questions()
|
||||
|
||||
config = ConfigurationOutput(archinstall.arguments)
|
||||
config = ConfigurationOutput(arch_config_handler.config)
|
||||
config.write_debug()
|
||||
config.save()
|
||||
|
||||
if archinstall.arguments.get('dry_run'):
|
||||
if arch_config_handler.args.dry_run:
|
||||
exit(0)
|
||||
|
||||
if not archinstall.arguments.get('silent'):
|
||||
if not arch_config_handler.args.silent:
|
||||
with Tui():
|
||||
if not config.confirm_config():
|
||||
debug('Installation aborted')
|
||||
_only_hd()
|
||||
|
||||
fs_handler = disk.FilesystemHandler(
|
||||
archinstall.arguments['disk_config'],
|
||||
archinstall.arguments.get('disk_encryption', None)
|
||||
)
|
||||
if arch_config_handler.config.disk_config:
|
||||
fs_handler = FilesystemHandler(
|
||||
arch_config_handler.config.disk_config,
|
||||
arch_config_handler.config.disk_encryption
|
||||
)
|
||||
|
||||
fs_handler.perform_filesystem_operations()
|
||||
perform_installation(archinstall.arguments.get('mount_point', Path('/mnt')))
|
||||
fs_handler.perform_filesystem_operations()
|
||||
|
||||
perform_installation(arch_config_handler.args.mountpoint)
|
||||
|
||||
|
||||
_only_hd()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "archinstall"
|
||||
dynamic = ["version"]
|
||||
version = "3.0.2"
|
||||
description = "Arch Linux installer - guided, templates etc."
|
||||
authors = [
|
||||
{name = "Anton Hvornum", email = "anton@hvornum.se"},
|
||||
|
|
@ -44,7 +44,6 @@ doc = ["sphinx"]
|
|||
archinstall = "archinstall:run_as_a_module"
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "archinstall.__version__"}
|
||||
readme = {file = ["README.rst", "USAGE.rst"]}
|
||||
|
||||
[tool.setuptools]
|
||||
|
|
@ -106,6 +105,11 @@ module = "archinstall.lib.disk.*"
|
|||
# 'Any' imports are allowed because pyparted doesn't have type hints
|
||||
disallow_any_unimported = false
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "archinstall.lib.models.*"
|
||||
# 'Any' imports are allowed because pyparted doesn't have type hints
|
||||
disallow_any_unimported = false
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "archinstall.lib.packages"
|
||||
disallow_any_explicit = true
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
"archinstall-language": "English",
|
||||
"audio_config": {"audio": "pipewire"},
|
||||
"bootloader": "Systemd-boot",
|
||||
"services": ["service_1", "service_2"],
|
||||
"disk_config": {
|
||||
"config_type": "default_layout",
|
||||
"device_modifications": [
|
||||
|
|
@ -107,7 +108,8 @@
|
|||
"sys_lang": "en_US"
|
||||
},
|
||||
"mirror_config": {
|
||||
"mirror-regions": {
|
||||
"custom_mirrors": [],
|
||||
"mirror_regions": {
|
||||
"Australia": [
|
||||
"http://archlinux.mirror.digitalpacific.com.au/$repo/os/$arch"
|
||||
]
|
||||
|
|
@ -150,7 +152,10 @@
|
|||
"main": "Desktop"
|
||||
}
|
||||
},
|
||||
"custom_commands": [
|
||||
"echo 'Hello, World!'"
|
||||
],
|
||||
"swap": false,
|
||||
"timezone": "UTC",
|
||||
"version": "2.8.6"
|
||||
"version": "3.0.2"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@ from pathlib import Path
|
|||
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
import archinstall
|
||||
from archinstall.default_profiles.profile import GreeterType
|
||||
from archinstall.lib.args import ArchConfig, ArchConfigHandler, Arguments
|
||||
from archinstall.lib.disk import DiskLayoutConfiguration, DiskLayoutType
|
||||
from archinstall.lib.hardware import GfxDriver
|
||||
from archinstall.lib.locale import LocaleConfiguration
|
||||
from archinstall.lib.mirrors import MirrorConfiguration
|
||||
from archinstall.lib.models import Audio, AudioConfiguration, Bootloader, NetworkConfiguration, User
|
||||
from archinstall.lib.mirrors import MirrorConfiguration, MirrorRegion
|
||||
from archinstall.lib.models import Audio, AudioConfiguration, Bootloader, DiskLayoutConfiguration, DiskLayoutType, NetworkConfiguration, User
|
||||
from archinstall.lib.models.locale import LocaleConfiguration
|
||||
from archinstall.lib.models.network_configuration import Nic, NicType
|
||||
from archinstall.lib.profile.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.models.profile_model import ProfileConfiguration
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.lib.translationhandler import translation_handler
|
||||
|
||||
|
|
@ -27,7 +25,7 @@ def test_default_args(monkeypatch: MonkeyPatch) -> None:
|
|||
silent=False,
|
||||
dry_run=False,
|
||||
script='guided',
|
||||
mount_point=Path('/mnt'),
|
||||
mountpoint=Path('/mnt'),
|
||||
skip_ntp=False,
|
||||
debug=False,
|
||||
offline=False,
|
||||
|
|
@ -77,7 +75,7 @@ def test_correct_parsing_args(
|
|||
silent=True,
|
||||
dry_run=True,
|
||||
script='execution_script',
|
||||
mount_point=Path('/tmp'),
|
||||
mountpoint=Path('/mnt'),
|
||||
skip_ntp=True,
|
||||
debug=True,
|
||||
offline=True,
|
||||
|
|
@ -102,13 +100,13 @@ def test_config_file_parsing(
|
|||
])
|
||||
|
||||
handler = ArchConfigHandler()
|
||||
arch_config = handler.arch_config
|
||||
arch_config = handler.config
|
||||
|
||||
# TODO: Use the real values from the test fixture instead of clearing out the entries
|
||||
arch_config.disk_config.device_modifications = [] # type: ignore[union-attr]
|
||||
|
||||
assert arch_config == ArchConfig(
|
||||
version=archinstall.__version__,
|
||||
version='3.0.2',
|
||||
locale_config=LocaleConfiguration(
|
||||
kb_layout='us',
|
||||
sys_lang='en_US',
|
||||
|
|
@ -141,7 +139,12 @@ def test_config_file_parsing(
|
|||
greeter=GreeterType.Lightdm
|
||||
),
|
||||
mirror_config=MirrorConfiguration(
|
||||
mirror_regions=[],
|
||||
mirror_regions=[
|
||||
MirrorRegion(
|
||||
name='Australia',
|
||||
urls=['http://archlinux.mirror.digitalpacific.com.au/$repo/os/$arch']
|
||||
)
|
||||
],
|
||||
custom_mirrors=[]
|
||||
),
|
||||
network_config=NetworkConfiguration(
|
||||
|
|
@ -170,6 +173,9 @@ def test_config_file_parsing(
|
|||
swap=False,
|
||||
timezone='UTC',
|
||||
additional_repositories=["testing"],
|
||||
_users=[User(username='user_name', password='user_pwd', sudo=True)],
|
||||
_disk_encryption=None
|
||||
users=[User(username='user_name', password='user_pwd', sudo=True)],
|
||||
disk_encryption=None,
|
||||
services=['service_1', 'service_2'],
|
||||
root_password='super_pwd',
|
||||
custom_commands=["echo 'Hello, World!'"]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
from archinstall.lib.args import ArchConfigHandler
|
||||
from archinstall.lib.configuration import ConfigurationOutput
|
||||
|
||||
|
||||
def test_user_config_roundtrip(
|
||||
monkeypatch: MonkeyPatch,
|
||||
config_fixture: Path,
|
||||
) -> None:
|
||||
monkeypatch.setattr('sys.argv', ['archinstall', '--config', str(config_fixture)])
|
||||
|
||||
handler = ArchConfigHandler()
|
||||
arch_config = handler.config
|
||||
|
||||
config_output = ConfigurationOutput(arch_config)
|
||||
|
||||
test_out_dir = Path('/tmp/')
|
||||
test_out_file = test_out_dir / config_output.user_configuration_file
|
||||
|
||||
config_output.save(test_out_dir)
|
||||
|
||||
result = json.loads(test_out_file.read_text())
|
||||
expected = json.loads(config_fixture.read_text())
|
||||
|
||||
# the parsed config will check if the given device exists otherwise
|
||||
# it will ignore the modification; as this test will run on various local systems
|
||||
# and the CI pipeline there's no good way specify a real device so we'll simply
|
||||
# copy the expected result to the actual result
|
||||
result['disk_config']['config_type'] = expected['disk_config']['config_type']
|
||||
result['disk_config']['device_modifications'] = expected['disk_config']['device_modifications']
|
||||
|
||||
assert sorted(result.items()) == sorted(expected.items())
|
||||
|
||||
|
||||
def test_creds_roundtrip(
|
||||
monkeypatch: MonkeyPatch,
|
||||
creds_fixture: Path,
|
||||
) -> None:
|
||||
monkeypatch.setattr('sys.argv', ['archinstall', '--creds', str(creds_fixture)])
|
||||
|
||||
handler = ArchConfigHandler()
|
||||
arch_config = handler.config
|
||||
|
||||
config_output = ConfigurationOutput(arch_config)
|
||||
|
||||
test_out_dir = Path('/tmp/')
|
||||
test_out_file = test_out_dir / config_output.user_credentials_file
|
||||
|
||||
config_output.save(test_out_dir)
|
||||
|
||||
result = json.loads(test_out_file.read_text())
|
||||
expected = json.loads(creds_fixture.read_text())
|
||||
|
||||
assert sorted(result.items()) == sorted(expected.items())
|
||||
Loading…
Reference in New Issue