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:
authorDaniel Girtler <blackrabbit256@gmail.com>2023-04-19 20:55:42 +1000
committerGitHub <noreply@github.com>2023-04-19 12:55:42 +0200
commit00b0ae7ba439a5a420095175b3bedd52c569db51 (patch)
treef02d081e361d5e65603f74dea3873dcc6606cf7c /archinstall/lib/models
parent5253e57e9f26cf3e59cb2460544af13f56e485bb (diff)
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 <girtler.daniel@gmail.com> Co-authored-by: Anton Hvornum <anton@hvornum.se> Co-authored-by: Anton Hvornum <anton.feeds+github@gmail.com> Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com>
Diffstat (limited to 'archinstall/lib/models')
-rw-r--r--archinstall/lib/models/__init__.py5
-rw-r--r--archinstall/lib/models/bootloader.py40
-rw-r--r--archinstall/lib/models/disk_encryption.py90
-rw-r--r--archinstall/lib/models/gen.py (renamed from archinstall/lib/models/dataclasses.py)48
-rw-r--r--archinstall/lib/models/network_configuration.py2
-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
9 files changed, 159 insertions, 407 deletions
diff --git a/archinstall/lib/models/__init__.py b/archinstall/lib/models/__init__.py
index 4a018b2c..8cc49ea0 100644
--- a/archinstall/lib/models/__init__.py
+++ b/archinstall/lib/models/__init__.py
@@ -1 +1,4 @@
-from .network_configuration import NetworkConfiguration as NetworkConfiguration \ No newline at end of file
+from .network_configuration import NetworkConfiguration, NicType, NetworkConfigurationHandler
+from .bootloader import Bootloader
+from .gen import VersionDef, PackageSearchResult, PackageSearch, LocalPackage
+from .users import PasswordStrength, User
diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py
new file mode 100644
index 00000000..38254c99
--- /dev/null
+++ b/archinstall/lib/models/bootloader.py
@@ -0,0 +1,40 @@
+from __future__ import annotations
+
+import logging
+import sys
+from enum import Enum
+from typing import List
+
+from ..hardware import has_uefi
+from ..output import log
+
+
+class Bootloader(Enum):
+ Systemd = 'Systemd-boot'
+ Grub = 'Grub'
+ Efistub = 'Efistub'
+
+ def json(self):
+ return self.value
+
+ @classmethod
+ def values(cls) -> List[str]:
+ return [e.value for e in cls]
+
+ @classmethod
+ def get_default(cls) -> Bootloader:
+ if 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())
+ log(f'Invalid bootloader value "{bootloader}". Allowed values: {values}', level=logging.WARN)
+ 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..cc8d7605 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
+
@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
@@ -83,12 +91,13 @@ class PackageSearchResult:
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
@@ -101,6 +110,7 @@ class PackageSearch:
def __post_init__(self):
self.results = [PackageSearchResult(**x) for x in self.results]
+
@dataclass
class LocalPackage:
name: str
@@ -129,8 +139,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..b7ab690d 100644
--- a/archinstall/lib/models/network_configuration.py
+++ b/archinstall/lib/models/network_configuration.py
@@ -93,7 +93,7 @@ class NetworkConfigurationHandler:
enable_services=True) # Sources the ISO network configuration to the install medium.
elif self._configuration.is_network_manager():
installation.add_additional_packages(["networkmanager"])
- if (profile := storage['arguments'].get('profile')) and profile.is_desktop_profile:
+ if (profile := storage['arguments'].get('profile_config')) and profile.is_desktop_type_profile:
installation.add_additional_packages(["network-manager-applet"])
installation.enable_service('NetworkManager.service')
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 = []