diff --git a/archinstall/default_profiles/servers/docker.py b/archinstall/default_profiles/servers/docker.py index 8ac0be36..d95e1720 100644 --- a/archinstall/default_profiles/servers/docker.py +++ b/archinstall/default_profiles/servers/docker.py @@ -27,5 +27,6 @@ class DockerProfile(Profile): def post_install(self, install_session: 'Installer') -> None: from archinstall.lib.args import arch_config_handler - for user in arch_config_handler.config.users: - install_session.arch_chroot(f'usermod -a -G docker {user.username}') + if auth_config := arch_config_handler.config.auth_config: + for user in auth_config.users: + install_session.arch_chroot(f'usermod -a -G docker {user.username}') diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index 6afe192b..e5d7998c 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -76,16 +76,15 @@ class ArchConfig: 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) - def unsafe_json(self) -> dict[str, Any]: - config: dict[str, list[UserSerialization] | str | None] = { - 'users': [user.json() for user in self.users], - } + config: dict[str, list[UserSerialization] | str | None] = {} - if self.auth_config and self.auth_config.root_enc_password: - config['root_enc_password'] = self.auth_config.root_enc_password.enc_password + if self.auth_config: + if self.auth_config.users: + config['users'] = [user.json() for user in self.auth_config.users] + + if self.auth_config.root_enc_password: + config['root_enc_password'] = self.auth_config.root_enc_password.enc_password if self.disk_config: disk_encryption = self.disk_config.disk_encryption @@ -177,13 +176,6 @@ class ArchConfig: if net_config := args_config.get('network_config', None): arch_config.network_config = NetworkConfiguration.parse_arg(net_config) - # DEPRECATED: backwards copatibility - if users := args_config.get('!users', None): - arch_config.users = User.parse_arguments(users) - - if users := args_config.get('users', None): - arch_config.users = User.parse_arguments(users) - if bootloader_config := args_config.get('bootloader', None): arch_config.bootloader = Bootloader.from_arg(bootloader_config) @@ -235,6 +227,19 @@ class ArchConfig: arch_config.auth_config = AuthenticationConfiguration() arch_config.auth_config.root_enc_password = root_password + # DEPRECATED: backwards copatibility + users: list[User] = [] + if args_users := args_config.get('!users', None): + users = User.parse_arguments(args_users) + + if args_users := args_config.get('users', None): + users = User.parse_arguments(args_users) + + if users: + if arch_config.auth_config is None: + arch_config.auth_config = AuthenticationConfiguration() + arch_config.auth_config.users = users + if custom_commands := args_config.get('custom_commands', []): arch_config.custom_commands = custom_commands diff --git a/archinstall/lib/authentication/authentication_handler.py b/archinstall/lib/authentication/authentication_handler.py index 5c8ed4de..f408cb94 100644 --- a/archinstall/lib/authentication/authentication_handler.py +++ b/archinstall/lib/authentication/authentication_handler.py @@ -18,11 +18,10 @@ class AuthenticationHandler: self, install_session: 'Installer', auth_config: AuthenticationConfiguration, - users: list['User'], hostname: str, ) -> None: - if auth_config.u2f_config and users is not None: - self._setup_u2f_login(install_session, auth_config.u2f_config, users, hostname) + if auth_config.u2f_config and auth_config.users is not None: + self._setup_u2f_login(install_session, auth_config.u2f_config, auth_config.users, hostname) def _setup_u2f_login(self, install_session: 'Installer', u2f_config: U2FLoginConfiguration, users: list[User], hostname: str) -> None: self._configure_u2f_mapping(install_session, u2f_config, users, hostname) diff --git a/archinstall/lib/authentication/authentication_menu.py b/archinstall/lib/authentication/authentication_menu.py index 9180e82f..2926e9cf 100644 --- a/archinstall/lib/authentication/authentication_menu.py +++ b/archinstall/lib/authentication/authentication_menu.py @@ -1,9 +1,11 @@ from typing import override from archinstall.lib.disk.fido import Fido2 +from archinstall.lib.interactions.manage_users_conf import ask_for_additional_users from archinstall.lib.menu.abstract_menu import AbstractSubMenu from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod -from archinstall.lib.models.users import Password +from archinstall.lib.models.users import Password, User +from archinstall.lib.output import FormattedOutput from archinstall.lib.translationhandler import tr from archinstall.lib.utils.util import get_password from archinstall.tui.curses_menu import SelectMenu @@ -41,6 +43,12 @@ class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]): preview_action=self._prev_root_pwd, key='root_enc_password', ), + MenuItem( + text=tr('User account'), + action=self._create_user_account, + preview_action=self._prev_users, + key='users', + ), MenuItem( text=tr('U2F login setup'), action=select_u2f_login, @@ -50,6 +58,18 @@ class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]): ), ] + def _create_user_account(self, preset: list[User] | None = None) -> list[User]: + preset = [] if preset is None else preset + users = ask_for_additional_users(defined_users=preset) + return users + + def _prev_users(self, item: MenuItem) -> str | None: + users: list[User] | None = item.value + + if users: + return FormattedOutput.as_table(users) + return None + def _prev_root_pwd(self, item: MenuItem) -> str | None: if item.value is not None: password: Password = item.value diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 8c55f942..05a79e4e 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -21,7 +21,6 @@ from .interactions.general_conf import ( ask_hostname, ask_ntp, ) -from .interactions.manage_users_conf import ask_for_additional_users from .interactions.network_menu import ask_to_configure_network from .interactions.system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_kernel from .locale.locale_menu import LocaleMenu @@ -33,7 +32,6 @@ from .models.mirrors import MirrorConfiguration from .models.network import NetworkConfiguration, NicType from .models.packages import Repository from .models.profile import ProfileConfiguration -from .models.users import User from .output import FormattedOutput from .pacman.config import PacmanConfig from .translationhandler import Language, tr, translation_handler @@ -115,12 +113,6 @@ class GlobalMenu(AbstractMenu[None]): preview_action=self._prev_authentication, key='auth_config', ), - MenuItem( - text=tr('User account'), - action=self._create_user_account, - preview_action=self._prev_users, - key='users', - ), MenuItem( text=tr('Profile'), action=self._select_profile, @@ -207,24 +199,20 @@ class GlobalMenu(AbstractMenu[None]): save_config(self._arch_config) def _missing_configs(self) -> list[str]: + item: MenuItem = self._item_group.find_by_key('auth_config') + auth_config: AuthenticationConfiguration | None = item.value + def check(s: str) -> bool: item = self._item_group.find_by_key(s) return item.has_value() def has_superuser() -> bool: - item = self._item_group.find_by_key('users') - - if item.has_value(): - users = item.value - if users: - return any([u.sudo for u in users]) + if auth_config and auth_config.users: + return any([u.sudo for u in auth_config.users]) return False missing = set() - item: MenuItem = self._item_group.find_by_key('auth_config') - auth_config: AuthenticationConfiguration | None = item.value - if (auth_config is None or auth_config.root_enc_password is None) and not has_superuser(): missing.add( tr('Either root-password or at least 1 user with sudo privileges must be specified'), @@ -312,6 +300,9 @@ class GlobalMenu(AbstractMenu[None]): if auth_config.root_enc_password: output += f'{tr("Root password")}: {auth_config.root_enc_password.hidden()}\n' + if auth_config.users: + output += FormattedOutput.as_table(auth_config.users) + '\n' + if auth_config.u2f_config: u2f_config = auth_config.u2f_config login_method = u2f_config.u2f_login_method.display_value() @@ -475,13 +466,6 @@ class GlobalMenu(AbstractMenu[None]): return None - def _prev_users(self, item: MenuItem) -> str | None: - users: list[User] | None = item.value - - if users: - return FormattedOutput.as_table(users) - return None - def _prev_profile(self, item: MenuItem) -> str | None: profile_config: ProfileConfiguration | None = item.value @@ -543,11 +527,6 @@ class GlobalMenu(AbstractMenu[None]): return packages - def _create_user_account(self, preset: list[User] | None = None) -> list[User]: - preset = [] if preset is None else preset - users = ask_for_additional_users(defined_users=preset) - return users - def _mirror_configuration(self, preset: MirrorConfiguration | None = None) -> MirrorConfiguration: mirror_configuration = MirrorMenu(preset=preset).run() diff --git a/archinstall/lib/models/authentication.py b/archinstall/lib/models/authentication.py index 74ead43c..31441387 100644 --- a/archinstall/lib/models/authentication.py +++ b/archinstall/lib/models/authentication.py @@ -1,8 +1,8 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum from typing import Any, NotRequired, TypedDict -from archinstall.lib.models.users import Password +from archinstall.lib.models.users import Password, User from archinstall.lib.translationhandler import tr @@ -60,6 +60,7 @@ class U2FLoginConfiguration: @dataclass class AuthenticationConfiguration: root_enc_password: Password | None = None + users: list[User] = field(default_factory=list) u2f_config: U2FLoginConfiguration | None = None @staticmethod diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 6de7fe09..bb16bad0 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -114,11 +114,10 @@ def perform_installation(mountpoint: Path) -> None: config.profile_config, ) - if users := config.users: - installation.create_users(users) - - if config.auth_config and config.users: - auth_handler.setup_auth(installation, config.auth_config, config.users, config.hostname) + if config.auth_config: + if config.auth_config.users: + installation.create_users(config.auth_config.users) + auth_handler.setup_auth(installation, config.auth_config, config.hostname) if config.packages and config.packages[0] != '': installation.add_additional_packages(config.packages) diff --git a/tests/test_args.py b/tests/test_args.py index ddfe7f45..db98ebb3 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -135,6 +135,14 @@ def test_config_file_parsing( ), auth_config=AuthenticationConfiguration( root_enc_password=Password(enc_password='password_hash'), + users=[ + User( + username='user_name', + password=Password(enc_password='password_hash'), + sudo=True, + groups=['wheel'], + ), + ], u2f_config=U2FLoginConfiguration( u2f_login_method=U2FLoginMethod.Passwordless, passwordless_sudo=True, @@ -215,14 +223,6 @@ def test_config_file_parsing( parallel_downloads=66, swap=False, timezone='UTC', - users=[ - User( - username='user_name', - password=Password(enc_password='password_hash'), - sudo=True, - groups=['wheel'], - ), - ], services=['service_1', 'service_2'], custom_commands=["echo 'Hello, World!'"], ) @@ -283,7 +283,7 @@ def test_deprecated_creds_config_parsing( assert arch_config.auth_config is not None assert arch_config.auth_config.root_enc_password == Password(plaintext='rootPwd') - assert arch_config.users == [ + assert arch_config.auth_config.users == [ User( username='user_name', password=Password(plaintext='userPwd'), @@ -334,7 +334,7 @@ def test_encrypted_creds_with_arg( assert arch_config.auth_config is not None assert arch_config.auth_config.root_enc_password == Password(enc_password='$y$j9T$FWCInXmSsS.8KV4i7O50H.$Hb6/g.Sw1ry888iXgkVgc93YNuVk/Rw94knDKdPVQw7') - assert arch_config.users == [ + assert arch_config.auth_config.users == [ User( username='t', password=Password(enc_password='$y$j9T$3KxMigAEnjtzbjalhLewE.$gmuoQtc9RNY/PmO/GxHHYvkZNO86Eeftg1Oc7L.QSO/'), @@ -363,7 +363,7 @@ def test_encrypted_creds_with_env_var( assert arch_config.auth_config is not None assert arch_config.auth_config.root_enc_password == Password(enc_password='$y$j9T$FWCInXmSsS.8KV4i7O50H.$Hb6/g.Sw1ry888iXgkVgc93YNuVk/Rw94knDKdPVQw7') - assert arch_config.users == [ + assert arch_config.auth_config.users == [ User( username='t', password=Password(enc_password='$y$j9T$3KxMigAEnjtzbjalhLewE.$gmuoQtc9RNY/PmO/GxHHYvkZNO86Eeftg1Oc7L.QSO/'),