Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall/lib/models
diff options
context:
space:
mode:
Diffstat (limited to 'archinstall/lib/models')
-rw-r--r--archinstall/lib/models/__init__.py10
-rw-r--r--archinstall/lib/models/audio_configuration.py54
-rw-r--r--archinstall/lib/models/bootloader.py47
-rw-r--r--archinstall/lib/models/disk_encryption.py90
-rw-r--r--archinstall/lib/models/gen.py (renamed from archinstall/lib/models/dataclasses.py)68
-rw-r--r--archinstall/lib/models/network_configuration.py261
-rw-r--r--archinstall/lib/models/password_strength.py85
-rw-r--r--archinstall/lib/models/pydantic.py134
-rw-r--r--archinstall/lib/models/subvolume.py68
-rw-r--r--archinstall/lib/models/users.py94
10 files changed, 352 insertions, 559 deletions
diff --git a/archinstall/lib/models/__init__.py b/archinstall/lib/models/__init__.py
index 4a018b2c..a1c90e48 100644
--- a/archinstall/lib/models/__init__.py
+++ b/archinstall/lib/models/__init__.py
@@ -1 +1,9 @@
-from .network_configuration import NetworkConfiguration as NetworkConfiguration \ No newline at end of file
+from .network_configuration import (
+ NetworkConfiguration,
+ NicType,
+ Nic
+)
+from .bootloader import Bootloader
+from .gen import VersionDef, PackageSearchResult, PackageSearch, LocalPackage
+from .users import PasswordStrength, User
+from .audio_configuration import Audio, AudioConfiguration
diff --git a/archinstall/lib/models/audio_configuration.py b/archinstall/lib/models/audio_configuration.py
new file mode 100644
index 00000000..88cd5d8e
--- /dev/null
+++ b/archinstall/lib/models/audio_configuration.py
@@ -0,0 +1,54 @@
+from dataclasses import dataclass
+from enum import Enum
+from typing import Any, TYPE_CHECKING, Dict
+
+from ..hardware import SysInfo
+from ..output import info
+from ...default_profiles.applications.pipewire import PipewireProfile
+
+if TYPE_CHECKING:
+ _: Any
+
+
+@dataclass
+class Audio(Enum):
+ Pipewire = 'pipewire'
+ Pulseaudio = 'pulseaudio'
+
+ @staticmethod
+ def no_audio_text() -> str:
+ return str(_('No audio server'))
+
+
+@dataclass
+class AudioConfiguration:
+ audio: Audio
+
+ def json(self) -> Dict[str, Any]:
+ return {
+ 'audio': self.audio.value
+ }
+
+ @staticmethod
+ def parse_arg(arg: Dict[str, Any]) -> 'AudioConfiguration':
+ return AudioConfiguration(
+ Audio(arg['audio'])
+ )
+
+ def install_audio_config(
+ self,
+ installation: Any
+ ):
+ info(f'Installing audio server: {self.audio.name}')
+
+ match self.audio:
+ case Audio.Pipewire:
+ PipewireProfile().install(installation)
+ case Audio.Pulseaudio:
+ installation.add_additional_packages("pulseaudio")
+
+ if SysInfo.requires_sof_fw():
+ installation.add_additional_packages('sof-firmware')
+
+ if SysInfo.requires_alsa_fw():
+ installation.add_additional_packages('alsa-firmware')
diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py
new file mode 100644
index 00000000..aa1a8e27
--- /dev/null
+++ b/archinstall/lib/models/bootloader.py
@@ -0,0 +1,47 @@
+from __future__ import annotations
+
+import sys
+from enum import Enum
+from typing import List
+
+from ..hardware import SysInfo
+from ..output import warn
+
+
+class Bootloader(Enum):
+ Systemd = 'Systemd-boot'
+ Grub = 'Grub'
+ Efistub = 'Efistub'
+ Limine = 'Limine'
+
+ def has_uki_support(self) -> bool:
+ match self:
+ case Bootloader.Efistub | Bootloader.Systemd:
+ return True
+ case _:
+ return False
+
+ def json(self) -> str:
+ return self.value
+
+ @staticmethod
+ def values() -> List[str]:
+ return [e.value for e in Bootloader]
+
+ @classmethod
+ def get_default(cls) -> Bootloader:
+ if SysInfo.has_uefi():
+ return Bootloader.Systemd
+ else:
+ return Bootloader.Grub
+
+ @classmethod
+ def from_arg(cls, bootloader: str) -> Bootloader:
+ # to support old configuration files
+ bootloader = bootloader.capitalize()
+
+ if bootloader not in cls.values():
+ values = ', '.join(cls.values())
+ warn(f'Invalid bootloader value "{bootloader}". Allowed values: {values}')
+ sys.exit(1)
+ return Bootloader(bootloader)
diff --git a/archinstall/lib/models/disk_encryption.py b/archinstall/lib/models/disk_encryption.py
deleted file mode 100644
index a4a501d9..00000000
--- a/archinstall/lib/models/disk_encryption.py
+++ /dev/null
@@ -1,90 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass, field
-from enum import Enum
-from typing import Optional, List, Dict, TYPE_CHECKING, Any
-
-from ..hsm.fido import Fido2Device
-
-if TYPE_CHECKING:
- _: Any
-
-
-class EncryptionType(Enum):
- Partition = 'partition'
-
- @classmethod
- def _encryption_type_mapper(cls) -> Dict[str, 'EncryptionType']:
- return {
- # str(_('Full disk encryption')): EncryptionType.FullDiskEncryption,
- str(_('Partition encryption')): EncryptionType.Partition
- }
-
- @classmethod
- def text_to_type(cls, text: str) -> 'EncryptionType':
- mapping = cls._encryption_type_mapper()
- return mapping[text]
-
- @classmethod
- def type_to_text(cls, type_: 'EncryptionType') -> str:
- mapping = cls._encryption_type_mapper()
- type_to_text = {type_: text for text, type_ in mapping.items()}
- return type_to_text[type_]
-
-
-@dataclass
-class DiskEncryption:
- encryption_type: EncryptionType = EncryptionType.Partition
- encryption_password: str = ''
- partitions: Dict[str, List[Dict[str, Any]]] = field(default_factory=dict)
- hsm_device: Optional[Fido2Device] = None
-
- @property
- def all_partitions(self) -> List[Dict[str, Any]]:
- _all: List[Dict[str, Any]] = []
- for parts in self.partitions.values():
- _all += parts
- return _all
-
- def generate_encryption_file(self, partition) -> bool:
- return partition in self.all_partitions and partition['mountpoint'] != '/'
-
- def json(self) -> Dict[str, Any]:
- obj = {
- 'encryption_type': self.encryption_type.value,
- 'partitions': self.partitions
- }
-
- if self.hsm_device:
- obj['hsm_device'] = self.hsm_device.json()
-
- return obj
-
- @classmethod
- def parse_arg(
- cls,
- disk_layout: Dict[str, Any],
- arg: Dict[str, Any],
- password: str = ''
- ) -> 'DiskEncryption':
- # we have to map the enc partition config to the disk layout objects
- # they both need to point to the same object as it will get modified
- # during the installation process
- enc_partitions: Dict[str, List[Dict[str, Any]]] = {}
-
- for path, partitions in disk_layout.items():
- conf_partitions = arg['partitions'].get(path, [])
- for part in partitions['partitions']:
- if part in conf_partitions:
- enc_partitions.setdefault(path, []).append(part)
-
- enc = DiskEncryption(
- EncryptionType(arg['encryption_type']),
- password,
- enc_partitions
- )
-
- if hsm := arg.get('hsm_device', None):
- enc.hsm_device = Fido2Device.parse_arg(hsm)
-
- return enc
diff --git a/archinstall/lib/models/dataclasses.py b/archinstall/lib/models/gen.py
index 99221fe3..fb7e5751 100644
--- a/archinstall/lib/models/dataclasses.py
+++ b/archinstall/lib/models/gen.py
@@ -1,16 +1,17 @@
from dataclasses import dataclass
-from typing import Optional, List
+from typing import Optional, List, Dict, Any
+
@dataclass
class VersionDef:
version_string: str
@classmethod
- def parse_version(self) -> List[str]:
- if '.' in self.version_string:
- versions = self.version_string.split('.')
+ def parse_version(cls) -> List[str]:
+ if '.' in cls.version_string:
+ versions = cls.version_string.split('.')
else:
- versions = [self.version_string]
+ versions = [cls.version_string]
return versions
@@ -19,37 +20,44 @@ class VersionDef:
return self.parse_version()[0]
@classmethod
- def minor(self) -> str:
- versions = self.parse_version()
+ def minor(cls) -> Optional[str]:
+ versions = cls.parse_version()
if len(versions) >= 2:
return versions[1]
+ return None
+
@classmethod
- def patch(self) -> str:
- versions = self.parse_version()
+ def patch(cls) -> Optional[str]:
+ versions = cls.parse_version()
if '-' in versions[-1]:
_, patch_version = versions[-1].split('-', 1)
return patch_version
- def __eq__(self, other :'VersionDef') -> bool:
+ 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 :'VersionDef') -> bool:
- if self.major > other.major:
+
+ def __lt__(self, other) -> bool:
+ if self.major() > other.major():
return False
- elif self.minor and other.minor and self.minor > other.minor:
+ elif self.minor() and other.minor() and self.minor() > other.minor():
return False
- elif self.patch and other.patch and self.patch > other.patch:
+ 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
@@ -79,16 +87,21 @@ 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
- def __eq__(self, other :'VersionDef') -> bool:
+ def __eq__(self, other) -> bool:
return self.pkg_version == other.pkg_version
- def __lt__(self, other :'VersionDef') -> bool:
+ def __lt__(self, other) -> bool:
return self.pkg_version < other.pkg_version
+
@dataclass
class PackageSearch:
version: int
@@ -98,8 +111,19 @@ 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
class LocalPackage:
@@ -129,8 +153,8 @@ class LocalPackage:
def pkg_version(self) -> str:
return self.version
- def __eq__(self, other :'VersionDef') -> bool:
+ def __eq__(self, other) -> bool:
return self.pkg_version == other.pkg_version
- def __lt__(self, other :'VersionDef') -> bool:
- return self.pkg_version < other.pkg_version \ No newline at end of file
+ def __lt__(self, other) -> bool:
+ return self.pkg_version < other.pkg_version
diff --git a/archinstall/lib/models/network_configuration.py b/archinstall/lib/models/network_configuration.py
index e026e97b..dfd8b8cb 100644
--- a/archinstall/lib/models/network_configuration.py
+++ b/archinstall/lib/models/network_configuration.py
@@ -1,183 +1,144 @@
from __future__ import annotations
-from dataclasses import dataclass
+from dataclasses import dataclass, field
from enum import Enum
-from typing import List, Optional, Dict, Union, Any, TYPE_CHECKING
+from typing import List, Optional, Dict, Any, TYPE_CHECKING, Tuple
-from ..output import log
-from ..storage import storage
+from ..profile import ProfileConfiguration
if TYPE_CHECKING:
_: Any
-class NicType(str, Enum):
+class NicType(Enum):
ISO = "iso"
NM = "nm"
MANUAL = "manual"
+ def display_msg(self) -> str:
+ match self:
+ case NicType.ISO:
+ return str(_('Copy ISO network configuration to installation'))
+ case NicType.NM:
+ return str(_('Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)'))
+ case NicType.MANUAL:
+ return str(_('Manual configuration'))
+
@dataclass
-class NetworkConfiguration:
- type: NicType
+class Nic:
iface: Optional[str] = None
ip: Optional[str] = None
dhcp: bool = True
gateway: Optional[str] = None
- dns: Union[None, List[str]] = None
-
- def __str__(self):
- if self.is_iso():
- return "Copy ISO configuration"
- elif self.is_network_manager():
- return "Use NetworkManager"
- elif self.is_manual():
- if self.dhcp:
- return f'iface={self.iface}, dhcp=auto'
- else:
- return f'iface={self.iface}, ip={self.ip}, dhcp=staticIp, gateway={self.gateway}, dns={self.dns}'
+ dns: List[str] = field(default_factory=list)
+
+ def table_data(self) -> Dict[str, Any]:
+ return {
+ 'iface': self.iface if self.iface else '',
+ 'ip': self.ip if self.ip else '',
+ 'dhcp': self.dhcp,
+ 'gateway': self.gateway if self.gateway else '',
+ 'dns': self.dns
+ }
+
+ def json(self) -> Dict[str, Any]:
+ return {
+ 'iface': self.iface,
+ 'ip': self.ip,
+ 'dhcp': self.dhcp,
+ 'gateway': self.gateway,
+ 'dns': self.dns
+ }
+
+ @staticmethod
+ def parse_arg(arg: Dict[str, Any]) -> Nic:
+ return Nic(
+ iface=arg.get('iface', None),
+ ip=arg.get('ip', None),
+ dhcp=arg.get('dhcp', True),
+ gateway=arg.get('gateway', None),
+ dns=arg.get('dns', []),
+ )
+
+ def as_systemd_config(self) -> str:
+ match: List[Tuple[str, str]] = []
+ network: List[Tuple[str, str]] = []
+
+ if self.iface:
+ match.append(('Name', self.iface))
+
+ if self.dhcp:
+ network.append(('DHCP', 'yes'))
else:
- return 'Unknown type'
-
- def as_json(self) -> Dict:
- exclude_fields = ['type']
- data = {}
- for k, v in self.__dict__.items():
- if k not in exclude_fields:
- if isinstance(v, list) and len(v) == 0:
- v = ''
- elif v is None:
- v = ''
-
- data[k] = v
-
- return data
+ if self.ip:
+ network.append(('Address', self.ip))
+ if self.gateway:
+ network.append(('Gateway', self.gateway))
+ for dns in self.dns:
+ network.append(('DNS', dns))
- def json(self) -> Dict:
- # for json serialization when calling json.dumps(...) on this class
- return self.__dict__
+ config = {'Match': match, 'Network': network}
- def is_iso(self) -> bool:
- return self.type == NicType.ISO
+ config_str = ''
+ for top, entries in config.items():
+ config_str += f'[{top}]\n'
+ config_str += '\n'.join([f'{k}={v}' for k, v in entries])
+ config_str += '\n\n'
- def is_network_manager(self) -> bool:
- return self.type == NicType.NM
+ return config_str
- def is_manual(self) -> bool:
- return self.type == NicType.MANUAL
+@dataclass
+class NetworkConfiguration:
+ type: NicType
+ nics: List[Nic] = field(default_factory=list)
-class NetworkConfigurationHandler:
- def __init__(self, config: Union[None, NetworkConfiguration, List[NetworkConfiguration]] = None):
- self._configuration = config
-
- @property
- def configuration(self):
- return self._configuration
+ def json(self) -> Dict[str, Any]:
+ config: Dict[str, Any] = {'type': self.type.value}
+ if self.nics:
+ config['nics'] = [n.json() for n in self.nics]
- def config_installer(self, installation: Any):
- if self._configuration is None:
- return
+ return config
- if isinstance(self._configuration, list):
- for config in self._configuration:
- installation.configure_nic(config)
+ @staticmethod
+ def parse_arg(config: Dict[str, Any]) -> Optional[NetworkConfiguration]:
+ nic_type = config.get('type', None)
+ if not nic_type:
+ return None
- installation.enable_service('systemd-networkd')
- installation.enable_service('systemd-resolved')
- else:
- # If user selected to copy the current ISO network configuration
- # Perform a copy of the config
- if self._configuration.is_iso():
+ match NicType(nic_type):
+ case NicType.ISO:
+ return NetworkConfiguration(NicType.ISO)
+ case NicType.NM:
+ return NetworkConfiguration(NicType.NM)
+ case NicType.MANUAL:
+ nics_arg = config.get('nics', [])
+ if nics_arg:
+ nics = [Nic.parse_arg(n) for n in nics_arg]
+ return NetworkConfiguration(NicType.MANUAL, nics)
+
+ return None
+
+ def install_network_config(
+ self,
+ installation: Any,
+ profile_config: Optional[ProfileConfiguration] = None
+ ):
+ match self.type:
+ case NicType.ISO:
installation.copy_iso_network_config(
- enable_services=True) # Sources the ISO network configuration to the install medium.
- elif self._configuration.is_network_manager():
+ enable_services=True # Sources the ISO network configuration to the install medium.
+ )
+ case NicType.NM:
installation.add_additional_packages(["networkmanager"])
- if (profile := storage['arguments'].get('profile')) and profile.is_desktop_profile:
- installation.add_additional_packages(["network-manager-applet"])
+ if profile_config and profile_config.profile:
+ if profile_config.profile.is_desktop_profile():
+ installation.add_additional_packages(["network-manager-applet"])
installation.enable_service('NetworkManager.service')
+ case NicType.MANUAL:
+ for nic in self.nics:
+ installation.configure_nic(nic)
- def _backwards_compability_config(self, config: Union[str,Dict[str, str]]) -> Union[List[NetworkConfiguration], NetworkConfiguration, None]:
- def get(config: Dict[str, str], key: str) -> List[str]:
- if (value := config.get(key, None)) is not None:
- return [value]
- return []
-
- if isinstance(config, str): # is a ISO network
- return NetworkConfiguration(NicType.ISO)
- elif config.get('NetworkManager'): # is a network manager configuration
- return NetworkConfiguration(NicType.NM)
- elif 'ip' in config:
- return [NetworkConfiguration(
- NicType.MANUAL,
- iface=config.get('nic', ''),
- ip=config.get('ip'),
- gateway=config.get('gateway', ''),
- dns=get(config, 'dns'),
- dhcp=False
- )]
- elif 'nic' in config:
- return [NetworkConfiguration(
- NicType.MANUAL,
- iface=config.get('nic', ''),
- dhcp=True
- )]
- else: # not recognized
- return None
-
- def _parse_manual_config(self, configs: List[Dict[str, Any]]) -> Optional[List[NetworkConfiguration]]:
- configurations = []
-
- for manual_config in configs:
- iface = manual_config.get('iface', None)
-
- if iface is None:
- log(_('No iface specified for manual configuration'))
- exit(1)
-
- if manual_config.get('dhcp', False) or not any([manual_config.get(v, '') for v in ['ip', 'gateway', 'dns']]):
- configurations.append(
- NetworkConfiguration(NicType.MANUAL, iface=iface)
- )
- else:
- ip = manual_config.get('ip', '')
- if not ip:
- log(_('Manual nic configuration with no auto DHCP requires an IP address'), fg='red')
- exit(1)
-
- configurations.append(
- NetworkConfiguration(
- NicType.MANUAL,
- iface=iface,
- ip=ip,
- gateway=manual_config.get('gateway', ''),
- dns=manual_config.get('dns', []),
- dhcp=False
- )
- )
-
- return configurations
-
- def _parse_nic_type(self, nic_type: str) -> NicType:
- try:
- return NicType(nic_type)
- except ValueError:
- options = [e.value for e in NicType]
- log(_('Unknown nic type: {}. Possible values are {}').format(nic_type, options), fg='red')
- exit(1)
-
- def parse_arguments(self, config: Any):
- if isinstance(config, list): # new data format
- self._configuration = self._parse_manual_config(config)
- elif nic_type := config.get('type', None): # new data format
- type_ = self._parse_nic_type(nic_type)
-
- if type_ != NicType.MANUAL:
- self._configuration = NetworkConfiguration(type_)
- else: # manual configuration settings
- self._configuration = self._parse_manual_config([config])
- else: # old style definitions
- network_config = self._backwards_compability_config(config)
- if network_config:
- return network_config
- return None
+ installation.enable_service('systemd-networkd')
+ installation.enable_service('systemd-resolved')
diff --git a/archinstall/lib/models/password_strength.py b/archinstall/lib/models/password_strength.py
deleted file mode 100644
index 61986bf0..00000000
--- a/archinstall/lib/models/password_strength.py
+++ /dev/null
@@ -1,85 +0,0 @@
-from enum import Enum
-
-
-class PasswordStrength(Enum):
- VERY_WEAK = 'very weak'
- WEAK = 'weak'
- MODERATE = 'moderate'
- STRONG = 'strong'
-
- @property
- def value(self):
- match self:
- case PasswordStrength.VERY_WEAK: return str(_('very weak'))
- case PasswordStrength.WEAK: return str(_('weak'))
- case PasswordStrength.MODERATE: return str(_('moderate'))
- case PasswordStrength.STRONG: return str(_('strong'))
-
- def color(self):
- match self:
- case PasswordStrength.VERY_WEAK: return 'red'
- case PasswordStrength.WEAK: return 'red'
- case PasswordStrength.MODERATE: return 'yellow'
- case PasswordStrength.STRONG: return 'green'
-
- @classmethod
- def strength(cls, password: str) -> 'PasswordStrength':
- digit = any(character.isdigit() for character in password)
- upper = any(character.isupper() for character in password)
- lower = any(character.islower() for character in password)
- symbol = any(not character.isalnum() for character in password)
- return cls._check_password_strength(digit, upper, lower, symbol, len(password))
-
- @classmethod
- def _check_password_strength(
- cls,
- digit: bool,
- upper: bool,
- lower: bool,
- symbol: bool,
- length: int
- ) -> 'PasswordStrength':
- # suggested evaluation
- # https://github.com/archlinux/archinstall/issues/1304#issuecomment-1146768163
- if digit and upper and lower and symbol:
- match length:
- case num if 13 <= num:
- return PasswordStrength.STRONG
- case num if 11 <= num <= 12:
- return PasswordStrength.MODERATE
- case num if 7 <= num <= 10:
- return PasswordStrength.WEAK
- case num if num <= 6:
- return PasswordStrength.VERY_WEAK
- elif digit and upper and lower:
- match length:
- case num if 14 <= num:
- return PasswordStrength.STRONG
- case num if 11 <= num <= 13:
- return PasswordStrength.MODERATE
- case num if 7 <= num <= 10:
- return PasswordStrength.WEAK
- case num if num <= 6:
- return PasswordStrength.VERY_WEAK
- elif upper and lower:
- match length:
- case num if 15 <= num:
- return PasswordStrength.STRONG
- case num if 12 <= num <= 14:
- return PasswordStrength.MODERATE
- case num if 7 <= num <= 11:
- return PasswordStrength.WEAK
- case num if num <= 6:
- return PasswordStrength.VERY_WEAK
- elif lower or upper:
- match length:
- case num if 18 <= num:
- return PasswordStrength.STRONG
- case num if 14 <= num <= 17:
- return PasswordStrength.MODERATE
- case num if 9 <= num <= 13:
- return PasswordStrength.WEAK
- case num if num <= 8:
- return PasswordStrength.VERY_WEAK
-
- return PasswordStrength.VERY_WEAK
diff --git a/archinstall/lib/models/pydantic.py b/archinstall/lib/models/pydantic.py
deleted file mode 100644
index 799e92af..00000000
--- a/archinstall/lib/models/pydantic.py
+++ /dev/null
@@ -1,134 +0,0 @@
-from typing import Optional, List
-from pydantic import BaseModel
-
-"""
-This python file is not in use.
-Pydantic is not a builtin, and we use the dataclasses.py instead!
-"""
-
-class VersionDef(BaseModel):
- version_string: str
-
- @classmethod
- def parse_version(self) -> List[str]:
- if '.' in self.version_string:
- versions = self.version_string.split('.')
- else:
- versions = [self.version_string]
-
- return versions
-
- @classmethod
- def major(self) -> str:
- return self.parse_version()[0]
-
- @classmethod
- def minor(self) -> str:
- versions = self.parse_version()
- if len(versions) >= 2:
- return versions[1]
-
- @classmethod
- def patch(self) -> str:
- versions = self.parse_version()
- if '-' in versions[-1]:
- _, patch_version = versions[-1].split('-', 1)
- return patch_version
-
- def __eq__(self, other :'VersionDef') -> bool:
- if other.major == self.major and \
- other.minor == self.minor and \
- other.patch == self.patch:
-
- return True
- return False
-
- def __lt__(self, other :'VersionDef') -> 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
-
- def __str__(self) -> str:
- return self.version_string
-
-
-class PackageSearchResult(BaseModel):
- 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 :'VersionDef') -> bool:
- return self.pkg_version == other.pkg_version
-
- def __lt__(self, other :'VersionDef') -> bool:
- return self.pkg_version < other.pkg_version
-
-
-class PackageSearch(BaseModel):
- version: int
- limit: int
- valid: bool
- results: List[PackageSearchResult]
-
-
-class LocalPackage(BaseModel):
- 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
-
- @property
- def pkg_version(self) -> str:
- return self.version
-
- def __eq__(self, other :'VersionDef') -> bool:
- return self.pkg_version == other.pkg_version
-
- def __lt__(self, other :'VersionDef') -> bool:
- return self.pkg_version < other.pkg_version \ No newline at end of file
diff --git a/archinstall/lib/models/subvolume.py b/archinstall/lib/models/subvolume.py
deleted file mode 100644
index 34a09227..00000000
--- a/archinstall/lib/models/subvolume.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from dataclasses import dataclass
-from typing import List, Any, Dict
-
-
-@dataclass
-class Subvolume:
- name: str
- mountpoint: str
- compress: bool = False
- nodatacow: bool = False
-
- def display(self) -> str:
- options_str = ','.join(self.options)
- return f'{_("Subvolume")}: {self.name:15} {_("Mountpoint")}: {self.mountpoint:20} {_("Options")}: {options_str}'
-
- @property
- def options(self) -> List[str]:
- options = [
- 'compress' if self.compress else '',
- 'nodatacow' if self.nodatacow else ''
- ]
- return [o for o in options if len(o)]
-
- def json(self) -> Dict[str, Any]:
- return {
- 'name': self.name,
- 'mountpoint': self.mountpoint,
- 'compress': self.compress,
- 'nodatacow': self.nodatacow
- }
-
- @classmethod
- def _parse(cls, config_subvolumes: List[Dict[str, Any]]) -> List['Subvolume']:
- subvolumes = []
- for entry in config_subvolumes:
- if not entry.get('name', None) or not entry.get('mountpoint', None):
- continue
-
- subvolumes.append(
- Subvolume(
- entry['name'],
- entry['mountpoint'],
- entry.get('compress', False),
- entry.get('nodatacow', False)
- )
- )
-
- return subvolumes
-
- @classmethod
- def _parse_backwards_compatible(cls, config_subvolumes) -> List['Subvolume']:
- subvolumes = []
- for name, mountpoint in config_subvolumes.items():
- if not name or not mountpoint:
- continue
-
- subvolumes.append(Subvolume(name, mountpoint))
-
- return subvolumes
-
- @classmethod
- def parse_arguments(cls, config_subvolumes: Any) -> List['Subvolume']:
- if isinstance(config_subvolumes, list):
- return cls._parse(config_subvolumes)
- elif isinstance(config_subvolumes, dict):
- return cls._parse_backwards_compatible(config_subvolumes)
-
- raise ValueError('Unknown disk layout btrfs subvolume format')
diff --git a/archinstall/lib/models/users.py b/archinstall/lib/models/users.py
index a8feb9ef..9ed70eef 100644
--- a/archinstall/lib/models/users.py
+++ b/archinstall/lib/models/users.py
@@ -1,12 +1,95 @@
from dataclasses import dataclass
from typing import Dict, List, Union, Any, TYPE_CHECKING
-
-from .password_strength import PasswordStrength
+from enum import Enum
if TYPE_CHECKING:
_: Any
+class PasswordStrength(Enum):
+ VERY_WEAK = 'very weak'
+ WEAK = 'weak'
+ MODERATE = 'moderate'
+ STRONG = 'strong'
+
+ @property
+ def value(self):
+ match self:
+ case PasswordStrength.VERY_WEAK: return str(_('very weak'))
+ case PasswordStrength.WEAK: return str(_('weak'))
+ case PasswordStrength.MODERATE: return str(_('moderate'))
+ case PasswordStrength.STRONG: return str(_('strong'))
+
+ def color(self):
+ match self:
+ case PasswordStrength.VERY_WEAK: return 'red'
+ case PasswordStrength.WEAK: return 'red'
+ case PasswordStrength.MODERATE: return 'yellow'
+ case PasswordStrength.STRONG: return 'green'
+
+ @classmethod
+ def strength(cls, password: str) -> 'PasswordStrength':
+ digit = any(character.isdigit() for character in password)
+ upper = any(character.isupper() for character in password)
+ lower = any(character.islower() for character in password)
+ symbol = any(not character.isalnum() for character in password)
+ return cls._check_password_strength(digit, upper, lower, symbol, len(password))
+
+ @classmethod
+ def _check_password_strength(
+ cls,
+ digit: bool,
+ upper: bool,
+ lower: bool,
+ symbol: bool,
+ length: int
+ ) -> 'PasswordStrength':
+ # suggested evaluation
+ # https://github.com/archlinux/archinstall/issues/1304#issuecomment-1146768163
+ if digit and upper and lower and symbol:
+ match length:
+ case num if 13 <= num:
+ return PasswordStrength.STRONG
+ case num if 11 <= num <= 12:
+ return PasswordStrength.MODERATE
+ case num if 7 <= num <= 10:
+ return PasswordStrength.WEAK
+ case num if num <= 6:
+ return PasswordStrength.VERY_WEAK
+ elif digit and upper and lower:
+ match length:
+ case num if 14 <= num:
+ return PasswordStrength.STRONG
+ case num if 11 <= num <= 13:
+ return PasswordStrength.MODERATE
+ case num if 7 <= num <= 10:
+ return PasswordStrength.WEAK
+ case num if num <= 6:
+ return PasswordStrength.VERY_WEAK
+ elif upper and lower:
+ match length:
+ case num if 15 <= num:
+ return PasswordStrength.STRONG
+ case num if 12 <= num <= 14:
+ return PasswordStrength.MODERATE
+ case num if 7 <= num <= 11:
+ return PasswordStrength.WEAK
+ case num if num <= 6:
+ return PasswordStrength.VERY_WEAK
+ elif lower or upper:
+ match length:
+ case num if 18 <= num:
+ return PasswordStrength.STRONG
+ case num if 14 <= num <= 17:
+ return PasswordStrength.MODERATE
+ case num if 9 <= num <= 13:
+ return PasswordStrength.WEAK
+ case num if num <= 8:
+ return PasswordStrength.VERY_WEAK
+
+ return PasswordStrength.VERY_WEAK
+
+
@dataclass
class User:
username: str
@@ -26,13 +109,6 @@ class User:
'sudo': self.sudo
}
- def display(self) -> str:
- password = '*' * (len(self.password) if self.password else 0)
- if password:
- strength = PasswordStrength.strength(self.password)
- password += f' ({strength.value})'
- return f'{_("Username")}: {self.username:16} {_("Password")}: {password:20} sudo: {str(self.sudo)}'
-
@classmethod
def _parse(cls, config_users: List[Dict[str, Any]]) -> List['User']:
users = []