Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall/lib/global_menu.py
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/global_menu.py
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/global_menu.py')
-rw-r--r--archinstall/lib/global_menu.py364
1 files changed, 364 insertions, 0 deletions
diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py
new file mode 100644
index 00000000..bc9164ee
--- /dev/null
+++ b/archinstall/lib/global_menu.py
@@ -0,0 +1,364 @@
+from __future__ import annotations
+
+from typing import Any, List, Optional, Union, Dict, TYPE_CHECKING
+
+from . import disk
+from .general import SysCommand, secret
+from .menu import Selector, AbstractMenu
+from .models import NetworkConfiguration
+from .models.bootloader import Bootloader
+from .models.users import User
+from .output import FormattedOutput
+from .profile.profile_menu import ProfileConfiguration
+from .storage import storage
+from .user_interaction import add_number_of_parrallel_downloads
+from .user_interaction import ask_additional_packages_to_install
+from .user_interaction import ask_for_additional_users
+from .user_interaction import ask_for_audio_selection
+from .user_interaction import ask_for_bootloader
+from .user_interaction import ask_for_swap
+from .user_interaction import ask_hostname
+from .user_interaction import ask_ntp
+from .user_interaction import ask_to_configure_network
+from .user_interaction import get_password, ask_for_a_timezone
+from .user_interaction import select_additional_repositories
+from .user_interaction import select_kernel
+from .user_interaction import select_language
+from .user_interaction import select_locale_enc
+from .user_interaction import select_locale_lang
+from .user_interaction import select_mirror_regions
+from .user_interaction.disk_conf import select_disk_config
+from .user_interaction.save_conf import save_config
+
+if TYPE_CHECKING:
+ _: Any
+
+
+class GlobalMenu(AbstractMenu):
+ def __init__(self, data_store: Dict[str, Any]):
+ super().__init__(data_store=data_store, auto_cursor=True, preview_size=0.3)
+
+ def setup_selection_menu_options(self):
+ # archinstall.Language will not use preset values
+ self._menu_options['archinstall-language'] = \
+ Selector(
+ _('Archinstall language'),
+ lambda x: self._select_archinstall_language(x),
+ display_func=lambda x: x.display_name,
+ default=self.translation_handler.get_language_by_abbr('en'))
+ self._menu_options['keyboard-layout'] = \
+ Selector(
+ _('Keyboard layout'),
+ lambda preset: select_language(preset),
+ default='us')
+ self._menu_options['mirror-region'] = \
+ Selector(
+ _('Mirror region'),
+ lambda preset: select_mirror_regions(preset),
+ display_func=lambda x: list(x.keys()) if x else '[]',
+ default={})
+ self._menu_options['sys-language'] = \
+ Selector(
+ _('Locale language'),
+ lambda preset: select_locale_lang(preset),
+ default='en_US')
+ self._menu_options['sys-encoding'] = \
+ Selector(
+ _('Locale encoding'),
+ lambda preset: select_locale_enc(preset),
+ default='UTF-8')
+ self._menu_options['disk_config'] = \
+ Selector(
+ _('Disk configuration'),
+ lambda preset: self._select_disk_config(preset),
+ preview_func=self._prev_disk_layouts,
+ display_func=lambda x: self._display_disk_layout(x),
+ )
+ self._menu_options['disk_encryption'] = \
+ Selector(
+ _('Disk encryption'),
+ lambda preset: self._disk_encryption(preset),
+ preview_func=self._prev_disk_encryption,
+ display_func=lambda x: self._display_disk_encryption(x),
+ dependencies=['disk_config'])
+ self._menu_options['swap'] = \
+ Selector(
+ _('Swap'),
+ lambda preset: ask_for_swap(preset),
+ default=True)
+ self._menu_options['bootloader'] = \
+ Selector(
+ _('Bootloader'),
+ lambda preset: ask_for_bootloader(preset),
+ display_func=lambda x: x.value,
+ default=Bootloader.get_default())
+ self._menu_options['hostname'] = \
+ Selector(
+ _('Hostname'),
+ lambda preset: ask_hostname(preset),
+ default='archlinux')
+ # root password won't have preset value
+ self._menu_options['!root-password'] = \
+ Selector(
+ _('Root password'),
+ lambda preset:self._set_root_password(),
+ display_func=lambda x: secret(x) if x else 'None')
+ self._menu_options['!users'] = \
+ Selector(
+ _('User account'),
+ lambda x: self._create_user_account(x),
+ default={},
+ display_func=lambda x: f'{len(x)} {_("User(s)")}' if len(x) > 0 else None,
+ preview_func=self._prev_users)
+ self._menu_options['profile_config'] = \
+ Selector(
+ _('Profile'),
+ lambda preset: self._select_profile(preset),
+ display_func=lambda x: x.profile.name if x else 'None',
+ preview_func=self._prev_profile
+ )
+ self._menu_options['audio'] = \
+ Selector(
+ _('Audio'),
+ lambda preset: self._select_audio(preset),
+ display_func=lambda x: x if x else 'None',
+ default=None
+ )
+ self._menu_options['parallel downloads'] = \
+ Selector(
+ _('Parallel Downloads'),
+ add_number_of_parrallel_downloads,
+ display_func=lambda x: x if x else '0',
+ default=0
+ )
+ self._menu_options['kernels'] = \
+ Selector(
+ _('Kernels'),
+ lambda preset: select_kernel(preset),
+ display_func=lambda x: ', '.join(x) if x else None,
+ default=['linux'])
+ self._menu_options['packages'] = \
+ Selector(
+ _('Additional packages'),
+ # lambda x: ask_additional_packages_to_install(storage['arguments'].get('packages', None)),
+ ask_additional_packages_to_install,
+ default=[])
+ self._menu_options['additional-repositories'] = \
+ Selector(
+ _('Optional repositories'),
+ select_additional_repositories,
+ display_func=lambda x: ', '.join(x) if x else None,
+ default=[])
+ self._menu_options['nic'] = \
+ Selector(
+ _('Network configuration'),
+ ask_to_configure_network,
+ display_func=lambda x: self._display_network_conf(x),
+ preview_func=self._prev_network_config,
+ default={})
+ self._menu_options['timezone'] = \
+ Selector(
+ _('Timezone'),
+ lambda preset: ask_for_a_timezone(preset),
+ default='UTC')
+ self._menu_options['ntp'] = \
+ Selector(
+ _('Automatic time sync (NTP)'),
+ lambda preset: self._select_ntp(preset),
+ default=True)
+ self._menu_options['__separator__'] = \
+ Selector('')
+ self._menu_options['save_config'] = \
+ Selector(
+ _('Save configuration'),
+ lambda preset: save_config(self._data_store),
+ no_store=True)
+ self._menu_options['install'] = \
+ Selector(
+ self._install_text(),
+ exec_func=lambda n,v: True if len(self._missing_configs()) == 0 else False,
+ preview_func=self._prev_install_missing_config,
+ no_store=True)
+
+ self._menu_options['abort'] = Selector(_('Abort'), exec_func=lambda n,v:exit(1))
+
+ def _update_install_text(self, name: str, value: str):
+ text = self._install_text()
+ self._menu_options['install'].update_description(text)
+
+ def post_callback(self, name: str, value: str):
+ self._update_install_text(name, value)
+
+ def _install_text(self):
+ missing = len(self._missing_configs())
+ if missing > 0:
+ return _('Install ({} config(s) missing)').format(missing)
+ return _('Install')
+
+ def _display_network_conf(self, cur_value: Union[NetworkConfiguration, List[NetworkConfiguration]]) -> str:
+ if not cur_value:
+ return _('Not configured, unavailable unless setup manually')
+ else:
+ if isinstance(cur_value, list):
+ return str(_('Configured {} interfaces')).format(len(cur_value))
+ else:
+ return str(cur_value)
+
+ def _disk_encryption(self, preset: Optional[disk.DiskEncryption]) -> Optional[disk.DiskEncryption]:
+ mods: Optional[List[disk.DeviceModification]] = self._menu_options['disk_config'].current_selection
+
+ if not mods:
+ # this should not happen as the encryption menu has the disk_config as dependency
+ raise ValueError('No disk layout specified')
+
+ data_store: Dict[str, Any] = {}
+ disk_encryption = disk.DiskEncryptionMenu(mods, data_store, preset=preset).run()
+ return disk_encryption
+
+ def _prev_network_config(self) -> Optional[str]:
+ selector = self._menu_options['nic']
+ if selector.has_selection():
+ ifaces = selector.current_selection
+ if isinstance(ifaces, list):
+ return FormattedOutput.as_table(ifaces)
+ return None
+
+ def _prev_disk_layouts(self) -> Optional[str]:
+ selector = self._menu_options['disk_config']
+ disk_layout_conf: Optional[disk.DiskLayoutConfiguration] = selector.current_selection
+
+ if disk_layout_conf:
+ device_mods: List[disk.DeviceModification] = \
+ list(filter(lambda x: len(x.partitions) > 0, disk_layout_conf.device_modifications))
+
+ if device_mods:
+ output_partition = '{}: {}\n'.format(str(_('Configuration')), disk_layout_conf.config_type.display_msg())
+ output_btrfs = ''
+
+ for mod in device_mods:
+ # create partition table
+ partition_table = FormattedOutput.as_table(mod.partitions)
+
+ output_partition += f'{mod.device_path}: {mod.device.device_info.model}\n'
+ output_partition += partition_table + '\n'
+
+ # create btrfs table
+ btrfs_partitions = list(
+ filter(lambda p: len(p.btrfs_subvols) > 0, mod.partitions)
+ )
+ for partition in btrfs_partitions:
+ output_btrfs += FormattedOutput.as_table(partition.btrfs_subvols) + '\n'
+
+ output = output_partition + output_btrfs
+ return output.rstrip()
+
+ return None
+
+ def _display_disk_layout(self, current_value: Optional[disk.DiskLayoutConfiguration] = None) -> str:
+ if current_value:
+ return current_value.config_type.display_msg()
+ return ''
+
+ def _prev_disk_encryption(self) -> Optional[str]:
+ encryption: Optional[disk.DiskEncryption] = self._menu_options['disk_encryption'].current_selection
+ if encryption:
+ enc_type = disk.EncryptionType.type_to_text(encryption.encryption_type)
+ output = str(_('Encryption type')) + f': {enc_type}\n'
+ output += str(_('Password')) + f': {secret(encryption.encryption_password)}\n'
+
+ if encryption.partitions:
+ output += 'Partitions: {} selected'.format(len(encryption.partitions)) + '\n'
+
+ if encryption.hsm_device:
+ output += f'HSM: {encryption.hsm_device.manufacturer}'
+
+ return output
+
+ return None
+
+ def _display_disk_encryption(self, current_value: Optional[disk.DiskEncryption]) -> str:
+ if current_value:
+ return disk.EncryptionType.type_to_text(current_value.encryption_type)
+ return ''
+
+ def _prev_install_missing_config(self) -> Optional[str]:
+ if missing := self._missing_configs():
+ text = str(_('Missing configurations:\n'))
+ for m in missing:
+ text += f'- {m}\n'
+ return text[:-1] # remove last new line
+ return None
+
+ def _prev_users(self) -> Optional[str]:
+ selector = self._menu_options['!users']
+ users: Optional[List[User]] = selector.current_selection
+
+ if users:
+ return FormattedOutput.as_table(users)
+ return None
+
+ def _prev_profile(self) -> Optional[str]:
+ selector = self._menu_options['profile_config']
+ profile_config: Optional[ProfileConfiguration] = selector.current_selection
+
+ if profile_config and profile_config.profile:
+ output = str(_('Profiles')) + ': '
+ if profile_names := profile_config.profile.current_selection_names():
+ output += ', '.join(profile_names) + '\n'
+ else:
+ output += profile_config.profile.name + '\n'
+
+ if profile_config.gfx_driver:
+ output += str(_('Graphics driver')) + ': ' + profile_config.gfx_driver + '\n'
+
+ if profile_config.greeter:
+ output += str(_('Greeter')) + ': ' + profile_config.greeter.value + '\n'
+
+ return output
+
+ return None
+
+ def _set_root_password(self) -> Optional[str]:
+ prompt = str(_('Enter root password (leave blank to disable root): '))
+ password = get_password(prompt=prompt)
+ return password
+
+ def _select_ntp(self, preset :bool = True) -> bool:
+ ntp = ask_ntp(preset)
+
+ value = str(ntp).lower()
+ SysCommand(f'timedatectl set-ntp {value}')
+
+ return ntp
+
+ def _select_disk_config(
+ self,
+ preset: Optional[disk.DiskLayoutConfiguration] = None
+ ) -> Optional[disk.DiskLayoutConfiguration]:
+ disk_config = select_disk_config(
+ preset,
+ storage['arguments'].get('advanced', False)
+ )
+
+ if disk_config != preset:
+ self._menu_options['disk_encryption'].set_current_selection(None)
+
+ return disk_config
+
+ def _select_profile(self, current_profile: Optional[ProfileConfiguration]):
+ from .profile.profile_menu import ProfileMenu
+ store: Dict[str, Any] = {}
+ profile_config = ProfileMenu(store, preset=current_profile).run()
+ return profile_config
+
+ def _select_audio(self, current: Union[str, None]) -> Optional[str]:
+ profile_config: Optional[ProfileConfiguration] = self._menu_options['profile_config'].current_selection
+ if profile_config and profile_config.profile:
+ is_desktop = profile_config.profile.is_desktop_profile() if profile_config else False
+ selection = ask_for_audio_selection(is_desktop, current)
+ return selection
+ return None
+
+ def _create_user_account(self, defined_users: List[User]) -> List[User]:
+ users = ask_for_additional_users(defined_users=defined_users)
+ return users