From 00b0ae7ba439a5a420095175b3bedd52c569db51 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Wed, 19 Apr 2023 20:55:42 +1000 Subject: PyParted and a large rewrite of the underlying partitioning (#1604) * Invert mypy files * Add optional pre-commit hooks * New profile structure * Serialize profiles * Use profile instead of classmethod * Custom profile setup * Separator between back * Support profile import via url * Move profiles module * Refactor files * Remove symlink * Add user to docker group * Update schema description * Handle list services * mypy fixes * mypy fixes * Rename profilesv2 to profiles * flake8 * mypy again * Support selecting DM * Fix mypy * Cleanup * Update greeter setting * Update schema * Revert toml changes * Poc external dependencies * Dependency support * New encryption menu * flake8 * Mypy and flake8 * Unify lsblk command * Update bootloader configuration * Git hooks * Fix import * Pyparted * Remove custom font setting * flake8 * Remove default preview * Manual partitioning menu * Update structure * Disk configuration * Update filesystem * luks2 encryption * Everything works until installation * Btrfsutil * Btrfs handling * Update btrfs * Save encryption config * Fix pipewire issue * Update mypy version * Update all pre-commit * Update package versions * Revert audio/pipewire * Merge master PRs * Add master changes * Merge master changes * Small renaming * Pull master changes * Reset disk enc after disk config change * Generate locals * Update naming * Fix imports * Fix broken sync * Fix pre selection on table menu * Profile menu * Update profile * Fix post_install * Added python-pyparted to PKGBUILD, this requires [testing] to be enabled in order to run makepkg. Package still works via python -m build etc. * Swaped around some setuptools logic in pyproject Since we define `package-data` and `packages` there should be no need for: ``` [tool.setuptools.packages.find] where = ["archinstall", "archinstall.*"] ``` * Removed pyproject collisions. Duplicate definitions. * Made sure pyproject.toml includes languages * Add example and update README * Fix pyproject issues * Generate locale * Refactor imports * Simplify imports * Add profile description and package examples * Align code * Fix mypy * Simplify imports * Fix saving config * Fix wrong luks merge * Refactor installation * Fix cdrom device loading * Fix wrongly merged code * Fix imports and greeter * Don't terminate on partprobe error * Use specific path on partprobe from luks * Update archinstall/lib/disk/device_model.py Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com> * Update archinstall/lib/disk/device_model.py Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com> * Update github workflow to test archinstall installation * Update sway merge * Generate locales * Update workflow --------- Co-authored-by: Daniel Girtler Co-authored-by: Anton Hvornum Co-authored-by: Anton Hvornum Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com> --- archinstall/lib/models/gen.py | 146 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 archinstall/lib/models/gen.py (limited to 'archinstall/lib/models/gen.py') diff --git a/archinstall/lib/models/gen.py b/archinstall/lib/models/gen.py new file mode 100644 index 00000000..cc8d7605 --- /dev/null +++ b/archinstall/lib/models/gen.py @@ -0,0 +1,146 @@ +from dataclasses import dataclass +from typing import Optional, List + + +@dataclass +class VersionDef: + version_string: str + + @classmethod + def parse_version(cls) -> List[str]: + if '.' in cls.version_string: + versions = cls.version_string.split('.') + else: + versions = [cls.version_string] + + return versions + + @classmethod + def major(self) -> str: + return self.parse_version()[0] + + @classmethod + def minor(cls) -> Optional[str]: + versions = cls.parse_version() + if len(versions) >= 2: + return versions[1] + + return None + + @classmethod + def patch(cls) -> Optional[str]: + versions = cls.parse_version() + if '-' in versions[-1]: + _, patch_version = versions[-1].split('-', 1) + return patch_version + + return None + + def __eq__(self, other) -> bool: + if other.major == self.major and \ + other.minor == self.minor and \ + other.patch == self.patch: + + return True + return False + + def __lt__(self, other) -> bool: + if self.major() > other.major(): + return False + elif self.minor() and other.minor() and self.minor() > other.minor(): + return False + elif self.patch() and other.patch() and self.patch() > other.patch(): + return False + + return True + + def __str__(self) -> str: + return self.version_string + + +@dataclass +class PackageSearchResult: + pkgname: str + pkgbase: str + repo: str + arch: str + pkgver: str + pkgrel: str + epoch: int + pkgdesc: str + url: str + filename: str + compressed_size: int + installed_size: int + build_date: str + last_update: str + flag_date: Optional[str] + maintainers: List[str] + packager: str + groups: List[str] + licenses: List[str] + conflicts: List[str] + provides: List[str] + replaces: List[str] + depends: List[str] + optdepends: List[str] + makedepends: List[str] + checkdepends: List[str] + + @property + def pkg_version(self) -> str: + return self.pkgver + + def __eq__(self, other) -> bool: + return self.pkg_version == other.pkg_version + + def __lt__(self, other) -> bool: + return self.pkg_version < other.pkg_version + + +@dataclass +class PackageSearch: + version: int + limit: int + valid: bool + num_pages: int + page: int + results: List[PackageSearchResult] + + def __post_init__(self): + self.results = [PackageSearchResult(**x) for x in self.results] + + +@dataclass +class LocalPackage: + name: str + version: str + description:str + architecture: str + url: str + licenses: str + groups: str + depends_on: str + optional_deps: str + required_by: str + optional_for: str + conflicts_with: str + replaces: str + installed_size: str + packager: str + build_date: str + install_date: str + install_reason: str + install_script: str + validated_by: str + provides: str + + @property + def pkg_version(self) -> str: + return self.version + + def __eq__(self, other) -> bool: + return self.pkg_version == other.pkg_version + + def __lt__(self, other) -> bool: + return self.pkg_version < other.pkg_version -- cgit v1.2.3-54-g00ecf From edbc13590366e93bb8a85eacf104d5613bc5793a Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 3 Oct 2023 18:31:17 +1100 Subject: Extend the mypy checks (#2120) * Extend the mypy checks * Update * Update * Update --------- Co-authored-by: Daniel Girtler --- archinstall/__init__.py | 4 ++-- archinstall/lib/disk/device_model.py | 12 ++++++------ archinstall/lib/disk/encryption_menu.py | 7 ++++--- archinstall/lib/global_menu.py | 11 +++++++---- archinstall/lib/installer.py | 14 ++++++++++---- archinstall/lib/menu/abstract_menu.py | 2 +- archinstall/lib/models/gen.py | 20 +++++++++++++++++--- archinstall/lib/packages/packages.py | 4 ++-- archinstall/lib/profile/profiles_handler.py | 6 +++--- archinstall/lib/translationhandler.py | 2 +- pyproject.toml | 2 +- 11 files changed, 54 insertions(+), 30 deletions(-) (limited to 'archinstall/lib/models/gen.py') diff --git a/archinstall/__init__.py b/archinstall/__init__.py index 07b85f96..11b47c48 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -225,8 +225,8 @@ def load_config(): if arguments.get('servers', None) is not None: storage['_selected_servers'] = arguments.get('servers', None) - if arguments.get('network_config', None) is not None: - config = NetworkConfiguration.parse_arg(arguments.get('network_config')) + 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: diff --git a/archinstall/lib/disk/device_model.py b/archinstall/lib/disk/device_model.py index 4ac53b0c..cd955851 100644 --- a/archinstall/lib/disk/device_model.py +++ b/archinstall/lib/disk/device_model.py @@ -308,9 +308,9 @@ class _PartitionInfo: start: Size length: Size flags: List[PartitionFlag] - partn: int - partuuid: str - uuid: str + partn: Optional[int] + partuuid: Optional[str] + uuid: Optional[str] disk: Disk mountpoints: List[Path] btrfs_subvol_infos: List[_BtrfsSubvolumeInfo] = field(default_factory=list) @@ -344,9 +344,9 @@ class _PartitionInfo: cls, partition: Partition, fs_type: Optional[FilesystemType], - partn: int, - partuuid: str, - uuid: str, + partn: Optional[int], + partuuid: Optional[str], + uuid: Optional[str], mountpoints: List[Path], btrfs_subvol_infos: List[_BtrfsSubvolumeInfo] = [] ) -> _PartitionInfo: diff --git a/archinstall/lib/disk/encryption_menu.py b/archinstall/lib/disk/encryption_menu.py index 234e3b03..c3a1c32f 100644 --- a/archinstall/lib/disk/encryption_menu.py +++ b/archinstall/lib/disk/encryption_menu.py @@ -3,6 +3,7 @@ from typing import Dict, Optional, Any, TYPE_CHECKING, List from ..disk import ( DeviceModification, + DiskLayoutConfiguration, PartitionModification, DiskEncryption, EncryptionType @@ -26,7 +27,7 @@ if TYPE_CHECKING: class DiskEncryptionMenu(AbstractSubMenu): def __init__( self, - mods: List[DeviceModification], + disk_config: DiskLayoutConfiguration, data_store: Dict[str, Any], preset: Optional[DiskEncryption] = None ): @@ -35,7 +36,7 @@ class DiskEncryptionMenu(AbstractSubMenu): else: self._preset = DiskEncryption() - self._modifications = mods + self._disk_config = disk_config super().__init__(data_store=data_store) def setup_selection_menu_options(self): @@ -59,7 +60,7 @@ class DiskEncryptionMenu(AbstractSubMenu): self._menu_options['partitions'] = \ Selector( _('Partitions'), - func=lambda preset: select_partitions_to_encrypt(self._modifications.device_modifications, preset), + func=lambda preset: select_partitions_to_encrypt(self._disk_config.device_modifications, preset), display_func=lambda x: f'{len(x)} {_("Partitions")}' if x else None, dependencies=['encryption_password'], default=self._preset.partitions, diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index b38dac0b..deda2ef6 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -176,8 +176,11 @@ class GlobalMenu(AbstractMenu): self._menu_options['abort'] = Selector(_('Abort'), exec_func=lambda n,v:exit(1)) def _missing_configs(self) -> List[str]: - def check(s): - return self._menu_options.get(s).has_selection() + def check(s) -> bool: + obj = self._menu_options.get(s) + if obj and obj.has_selection(): + return True + return False def has_superuser() -> bool: sel = self._menu_options['!users'] @@ -228,7 +231,7 @@ class GlobalMenu(AbstractMenu): return config.type.display_msg() def _disk_encryption(self, preset: Optional[disk.DiskEncryption]) -> Optional[disk.DiskEncryption]: - mods: Optional[List[disk.DeviceModification]] = self._menu_options['disk_config'].current_selection + mods: Optional[disk.DiskLayoutConfiguration] = self._menu_options['disk_config'].current_selection if not mods: # this should not happen as the encryption menu has the disk_config as dependency @@ -263,7 +266,7 @@ class GlobalMenu(AbstractMenu): def _prev_additional_pkgs(self): selector = self._menu_options['packages'] - if selector.has_selection(): + if selector.current_selection: packages: List[str] = selector.current_selection return format_cols(packages, None) return None diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index ad98d9a8..8e716d3d 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -131,7 +131,11 @@ class Installer: We need to wait for it before we continue since we opted in to use a custom mirror/region. """ info('Waiting for time sync (systemd-timesyncd.service) to complete.') - while SysCommand('timedatectl show --property=NTPSynchronized --value').decode() != 'yes': + + while True: + time_val = SysCommand('timedatectl show --property=NTPSynchronized --value').decode() + if time_val and time_val.strip() == 'yes': + break time.sleep(1) info('Waiting for automatic mirror selection (reflector) to complete.') @@ -237,7 +241,7 @@ class Installer: gen_enc_file = self._disk_encryption.should_generate_encryption_file(part_mod) luks_handler = Luks2( - part_mod.dev_path, + part_mod.safe_dev_path, mapper_name=part_mod.mapper_name, password=self._disk_encryption.encryption_password ) @@ -281,8 +285,10 @@ class Installer: self._fstab_entries.append(f'{file} none swap defaults 0 0') if enable_resume: - resume_uuid = SysCommand(f'findmnt -no UUID -T {self.target}{file}').decode('UTF-8').strip() - resume_offset = SysCommand(f'/usr/bin/filefrag -v {self.target}{file}').decode().split('0:', 1)[1].split(":", 1)[1].split("..", 1)[0].strip() + resume_uuid = SysCommand(f'findmnt -no UUID -T {self.target}{file}').decode() + resume_offset = SysCommand( + f'/usr/bin/filefrag -v {self.target}{file}' + ).decode().split('0:', 1)[1].split(":", 1)[1].split("..", 1)[0].strip() self._hooks.append('resume') self._kernel_params.append(f'resume=UUID={resume_uuid}') diff --git a/archinstall/lib/menu/abstract_menu.py b/archinstall/lib/menu/abstract_menu.py index 306c500a..053f3c30 100644 --- a/archinstall/lib/menu/abstract_menu.py +++ b/archinstall/lib/menu/abstract_menu.py @@ -14,7 +14,7 @@ class Selector: def __init__( self, description: str, - func: Optional[Callable[[str], Any]] = None, + func: Optional[Callable[[Any], Any]] = None, display_func: Optional[Callable] = None, default: Optional[Any] = None, enabled: bool = False, diff --git a/archinstall/lib/models/gen.py b/archinstall/lib/models/gen.py index cc8d7605..fb7e5751 100644 --- a/archinstall/lib/models/gen.py +++ b/archinstall/lib/models/gen.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Optional, List +from typing import Optional, List, Dict, Any @dataclass @@ -87,6 +87,10 @@ class PackageSearchResult: makedepends: List[str] checkdepends: List[str] + @staticmethod + def from_json(data: Dict[str, Any]) -> 'PackageSearchResult': + return PackageSearchResult(**data) + @property def pkg_version(self) -> str: return self.pkgver @@ -107,8 +111,18 @@ class PackageSearch: page: int results: List[PackageSearchResult] - def __post_init__(self): - self.results = [PackageSearchResult(**x) for x in self.results] + @staticmethod + def from_json(data: Dict[str, Any]) -> 'PackageSearch': + results = [PackageSearchResult.from_json(r) for r in data['results']] + + return PackageSearch( + version=data['version'], + limit=data['limit'], + valid=data['valid'], + num_pages=data['num_pages'], + page=data['page'], + results=results + ) @dataclass diff --git a/archinstall/lib/packages/packages.py b/archinstall/lib/packages/packages.py index 7491df07..e495b03f 100644 --- a/archinstall/lib/packages/packages.py +++ b/archinstall/lib/packages/packages.py @@ -55,8 +55,8 @@ def package_search(package :str) -> PackageSearch: raise PackageError(f"Could not locate package: [{response.code}] {response}") data = response.read().decode('UTF-8') - - return PackageSearch(**json.loads(data)) + json_data = json.loads(data) + return PackageSearch.from_json(json_data) def find_package(package :str) -> List[PackageSearchResult]: diff --git a/archinstall/lib/profile/profiles_handler.py b/archinstall/lib/profile/profiles_handler.py index 03039321..515cdfe9 100644 --- a/archinstall/lib/profile/profiles_handler.py +++ b/archinstall/lib/profile/profiles_handler.py @@ -138,16 +138,16 @@ class ProfileHandler: profiles = [profiles] for profile in profiles: - self._profiles.append(profile) + self.profiles.append(profile) - self._verify_unique_profile_names(self._profiles) + self._verify_unique_profile_names(self.profiles) def remove_custom_profiles(self, profiles: Union[TProfile, List[TProfile]]): if not isinstance(profiles, list): profiles = [profiles] remove_names = [p.name for p in profiles] - self._profiles = [p for p in self._profiles if p.name not in remove_names] + self._profiles = [p for p in self.profiles if p.name not in remove_names] def get_profile_by_name(self, name: str) -> Optional[Profile]: return next(filter(lambda x: x.name == name, self.profiles), None) # type: ignore diff --git a/archinstall/lib/translationhandler.py b/archinstall/lib/translationhandler.py index 33230562..3ea4c70e 100644 --- a/archinstall/lib/translationhandler.py +++ b/archinstall/lib/translationhandler.py @@ -206,4 +206,4 @@ class DeferredTranslation: @classmethod def install(cls): import builtins - builtins._ = cls + builtins._ = cls # type: ignore diff --git a/pyproject.toml b/pyproject.toml index 36ee0492..445aa277 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ packages = ["archinstall"] python_version = "3.11" files = "archinstall/" exclude = "tests" -#check_untyped_defs=true +check_untyped_defs=true [tool.bandit] targets = ["archinstall"] -- cgit v1.2.3-54-g00ecf