Revert "Move root password menu into authentication menu (#3650)"

This reverts commit e0c3bb3869.
This commit is contained in:
Daniel Girtler 2025-07-18 05:58:06 +00:00 committed by GitHub
parent e0c3bb3869
commit 8d29fbd07f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 39 additions and 71 deletions

View File

@ -23,8 +23,6 @@ from archinstall.lib.models.network_configuration import NetworkConfiguration
from archinstall.lib.models.packages import Repository from archinstall.lib.models.packages import Repository
from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.models.profile import ProfileConfiguration
from archinstall.lib.models.users import Password, User from archinstall.lib.models.users import Password, User
from archinstall.lib.models.profile_model import ProfileConfiguration
from archinstall.lib.models.users import Password, User, UserSerialization
from archinstall.lib.output import debug, error, logger, warn from archinstall.lib.output import debug, error, logger, warn
from archinstall.lib.plugins import load_plugin from archinstall.lib.plugins import load_plugin
from archinstall.lib.translationhandler import Language, tr, translation_handler from archinstall.lib.translationhandler import Language, tr, translation_handler
@ -80,15 +78,14 @@ class ArchConfig:
# Special fields that should be handle with care due to security implications # Special fields that should be handle with care due to security implications
users: list[User] = field(default_factory=list) users: list[User] = field(default_factory=list)
root_enc_password: Password | None = None
def unsafe_json(self) -> dict[str, Any]: def unsafe_json(self) -> dict[str, Any]:
config: dict[str, list[UserSerialization] | str | None] = { config = {
'users': [user.json() for user in self.users], 'users': [user.json() for user in self.users],
'root_enc_password': self.root_enc_password.enc_password if self.root_enc_password else 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.disk_config: if self.disk_config:
disk_encryption = self.disk_config.disk_encryption disk_encryption = self.disk_config.disk_encryption
if disk_encryption and disk_encryption.encryption_password: if disk_encryption and disk_encryption.encryption_password:
@ -225,17 +222,11 @@ class ArchConfig:
arch_config.services = services arch_config.services = services
# DEPRECATED: backwards compatibility # DEPRECATED: backwards compatibility
root_password = None
if root_password := args_config.get('!root-password', None): if root_password := args_config.get('!root-password', None):
root_password = Password(plaintext=root_password) arch_config.root_enc_password = Password(plaintext=root_password)
if enc_password := args_config.get('root_enc_password', None): if enc_password := args_config.get('root_enc_password', None):
root_password = Password(enc_password=enc_password) arch_config.root_enc_password = Password(enc_password=enc_password)
if root_password is not None:
if arch_config.auth_config is None:
arch_config.auth_config = AuthenticationConfiguration()
arch_config.auth_config.root_enc_password = root_password
if custom_commands := args_config.get('custom_commands', []): if custom_commands := args_config.get('custom_commands', []):
arch_config.custom_commands = custom_commands arch_config.custom_commands = custom_commands

View File

@ -3,9 +3,7 @@ from typing import override
from archinstall.lib.disk.fido import Fido2 from archinstall.lib.disk.fido import Fido2
from archinstall.lib.menu.abstract_menu import AbstractSubMenu from archinstall.lib.menu.abstract_menu import AbstractSubMenu
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
from archinstall.lib.models.users import Password
from archinstall.lib.translationhandler import tr from archinstall.lib.translationhandler import tr
from archinstall.lib.utils.util import get_password
from archinstall.tui.curses_menu import SelectMenu from archinstall.tui.curses_menu import SelectMenu
from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType from archinstall.tui.result import ResultType
@ -35,12 +33,6 @@ class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]):
def _define_menu_options(self) -> list[MenuItem]: def _define_menu_options(self) -> list[MenuItem]:
return [ return [
MenuItem(
text=tr('Root password'),
action=select_root_password,
preview_action=self._prev_root_pwd,
key='root_enc_password',
),
MenuItem( MenuItem(
text=tr('U2F login setup'), text=tr('U2F login setup'),
action=select_u2f_login, action=select_u2f_login,
@ -50,18 +42,6 @@ class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]):
), ),
] ]
def _prev_root_pwd(self, item: MenuItem) -> str | None:
if item.value is not None:
password: Password = item.value
return f'{tr("Root password")}: {password.hidden()}'
return None
def _depends_on_u2f(self) -> bool:
devices = Fido2.get_fido2_devices()
if not devices:
return False
return True
def _prev_u2f_login(self, item: MenuItem) -> str | None: def _prev_u2f_login(self, item: MenuItem) -> str | None:
if item.value is not None: if item.value is not None:
u2f_config: U2FLoginConfiguration = item.value u2f_config: U2FLoginConfiguration = item.value
@ -81,11 +61,6 @@ class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]):
return None return None
def select_root_password(preset: str | None = None) -> Password | None:
password = get_password(text=tr('Root password'), allow_skip=True)
return password
def select_u2f_login(preset: U2FLoginConfiguration) -> U2FLoginConfiguration | None: def select_u2f_login(preset: U2FLoginConfiguration) -> U2FLoginConfiguration | None:
devices = Fido2.get_fido2_devices() devices = Fido2.get_fido2_devices()
if not devices: if not devices:

View File

@ -34,11 +34,10 @@ from .models.network_configuration import NetworkConfiguration, NicType
from .models.packages import Repository from .models.packages import Repository
from .models.profile import ProfileConfiguration from .models.profile import ProfileConfiguration
from .models.users import Password, User from .models.users import Password, User
from .models.profile_model import ProfileConfiguration
from .models.users import User
from .output import FormattedOutput from .output import FormattedOutput
from .pacman.config import PacmanConfig from .pacman.config import PacmanConfig
from .translationhandler import Language, tr, translation_handler from .translationhandler import Language, tr, translation_handler
from .utils.util import get_password
class GlobalMenu(AbstractMenu[None]): class GlobalMenu(AbstractMenu[None]):
@ -111,9 +110,16 @@ class GlobalMenu(AbstractMenu[None]):
preview_action=self._prev_hostname, preview_action=self._prev_hostname,
key='hostname', key='hostname',
), ),
MenuItem(
text=tr('Root password'),
action=self._set_root_password,
preview_action=self._prev_root_pwd,
key='root_enc_password',
),
MenuItem( MenuItem(
text=tr('Authentication'), text=tr('Authentication'),
action=self._select_authentication, action=self._select_authentication,
value=[],
preview_action=self._prev_authentication, preview_action=self._prev_authentication,
key='auth_config', key='auth_config',
), ),
@ -224,16 +230,13 @@ class GlobalMenu(AbstractMenu[None]):
missing = set() 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'),
)
for item in self._item_group.items: for item in self._item_group.items:
if item.mandatory: if item.key in ['root_enc_password', 'users']:
if not check('root_enc_password') and not has_superuser():
missing.add(
tr('Either root-password or at least 1 user with sudo privileges must be specified'),
)
elif item.mandatory:
assert item.key is not None assert item.key is not None
if not check(item.key): if not check(item.key):
missing.add(item.text) missing.add(item.text)
@ -311,9 +314,6 @@ class GlobalMenu(AbstractMenu[None]):
auth_config: AuthenticationConfiguration = item.value auth_config: AuthenticationConfiguration = item.value
output = '' output = ''
if auth_config.root_enc_password:
output += f'{tr("Root password")}: {auth_config.root_enc_password.hidden()}\n'
if auth_config.u2f_config: if auth_config.u2f_config:
u2f_config = auth_config.u2f_config u2f_config = auth_config.u2f_config
login_method = u2f_config.u2f_login_method.display_value() login_method = u2f_config.u2f_login_method.display_value()
@ -400,6 +400,12 @@ class GlobalMenu(AbstractMenu[None]):
return f'{tr("Hostname")}: {item.value}' return f'{tr("Hostname")}: {item.value}'
return None return None
def _prev_root_pwd(self, item: MenuItem) -> str | None:
if item.value is not None:
password: Password = item.value
return f'{tr("Root password")}: {password.hidden()}'
return None
def _prev_parallel_dw(self, item: MenuItem) -> str | None: def _prev_parallel_dw(self, item: MenuItem) -> str | None:
if item.value is not None: if item.value is not None:
return f'{tr("Parallel Downloads")}: {item.value}' return f'{tr("Parallel Downloads")}: {item.value}'
@ -504,6 +510,10 @@ class GlobalMenu(AbstractMenu[None]):
return None return None
def _set_root_password(self, preset: str | None = None) -> Password | None:
password = get_password(text=tr('Root password'), allow_skip=True)
return password
def _select_disk_config( def _select_disk_config(
self, self,
preset: DiskLayoutConfiguration | None = None, preset: DiskLayoutConfiguration | None = None,

View File

@ -2,7 +2,6 @@ from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Any, NotRequired, TypedDict from typing import Any, NotRequired, TypedDict
from archinstall.lib.models.users import Password
from archinstall.lib.translationhandler import tr from archinstall.lib.translationhandler import tr
@ -59,7 +58,6 @@ class U2FLoginConfiguration:
@dataclass @dataclass
class AuthenticationConfiguration: class AuthenticationConfiguration:
root_enc_password: Password | None = None
u2f_config: U2FLoginConfiguration | None = None u2f_config: U2FLoginConfiguration | None = None
@staticmethod @staticmethod
@ -69,9 +67,6 @@ class AuthenticationConfiguration:
if (u2f_config := args.get('u2f_config')) is not None: if (u2f_config := args.get('u2f_config')) is not None:
auth_config.u2f_config = U2FLoginConfiguration.parse_arg(u2f_config) auth_config.u2f_config = U2FLoginConfiguration.parse_arg(u2f_config)
if enc_password := args.get('root_enc_password'):
auth_config.root_enc_password = Password(enc_password=enc_password)
return auth_config return auth_config
def json(self) -> AuthenticationSerialization: def json(self) -> AuthenticationSerialization:

View File

@ -100,8 +100,8 @@ class PasswordStrength(Enum):
return PasswordStrength.VERY_WEAK return PasswordStrength.VERY_WEAK
UserSerialization = TypedDict( _UserSerialization = TypedDict(
'UserSerialization', '_UserSerialization',
{ {
'username': str, 'username': str,
'!password': NotRequired[str], '!password': NotRequired[str],
@ -173,7 +173,7 @@ class User:
'groups': self.groups, 'groups': self.groups,
} }
def json(self) -> UserSerialization: def json(self) -> _UserSerialization:
return { return {
'username': self.username, 'username': self.username,
'enc_password': self.password.enc_password, 'enc_password': self.password.enc_password,
@ -184,7 +184,7 @@ class User:
@classmethod @classmethod
def parse_arguments( def parse_arguments(
cls, cls,
args: list[UserSerialization], args: list[_UserSerialization],
) -> list['User']: ) -> list['User']:
users: list[User] = [] users: list[User] = []

View File

@ -138,8 +138,8 @@ def perform_installation(mountpoint: Path) -> None:
if accessibility_tools_in_use(): if accessibility_tools_in_use():
installation.enable_espeakup() installation.enable_espeakup()
if config.auth_config and config.auth_config.root_enc_password: if root_pw := config.root_enc_password:
root_user = User('root', config.auth_config.root_enc_password, False) root_user = User('root', root_pw, False)
installation.set_user_password(root_user) installation.set_user_password(root_user)
if (profile_config := config.profile_config) and profile_config.profile: if (profile_config := config.profile_config) and profile_config.profile:

View File

@ -134,7 +134,6 @@ def test_config_file_parsing(
audio_config=AudioConfiguration(audio=Audio.PIPEWIRE), audio_config=AudioConfiguration(audio=Audio.PIPEWIRE),
), ),
auth_config=AuthenticationConfiguration( auth_config=AuthenticationConfiguration(
root_enc_password=Password(enc_password='password_hash'),
u2f_config=U2FLoginConfiguration( u2f_config=U2FLoginConfiguration(
u2f_login_method=U2FLoginMethod.Passwordless, u2f_login_method=U2FLoginMethod.Passwordless,
passwordless_sudo=True, passwordless_sudo=True,
@ -224,6 +223,7 @@ def test_config_file_parsing(
), ),
], ],
services=['service_1', 'service_2'], services=['service_1', 'service_2'],
root_enc_password=Password(enc_password='password_hash'),
custom_commands=["echo 'Hello, World!'"], custom_commands=["echo 'Hello, World!'"],
) )
@ -280,8 +280,7 @@ def test_deprecated_creds_config_parsing(
handler = ArchConfigHandler() handler = ArchConfigHandler()
arch_config = handler.config arch_config = handler.config
assert arch_config.auth_config is not None assert arch_config.root_enc_password == Password(plaintext='rootPwd')
assert arch_config.auth_config.root_enc_password == Password(plaintext='rootPwd')
assert arch_config.users == [ assert arch_config.users == [
User( User(
@ -332,8 +331,7 @@ def test_encrypted_creds_with_arg(
handler = ArchConfigHandler() handler = ArchConfigHandler()
arch_config = handler.config arch_config = handler.config
assert arch_config.auth_config is not None assert arch_config.root_enc_password == Password(enc_password='$y$j9T$FWCInXmSsS.8KV4i7O50H.$Hb6/g.Sw1ry888iXgkVgc93YNuVk/Rw94knDKdPVQw7')
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.users == [
User( User(
username='t', username='t',
@ -361,8 +359,7 @@ def test_encrypted_creds_with_env_var(
handler = ArchConfigHandler() handler = ArchConfigHandler()
arch_config = handler.config arch_config = handler.config
assert arch_config.auth_config is not None assert arch_config.root_enc_password == Password(enc_password='$y$j9T$FWCInXmSsS.8KV4i7O50H.$Hb6/g.Sw1ry888iXgkVgc93YNuVk/Rw94knDKdPVQw7')
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.users == [
User( User(
username='t', username='t',