From 27aa94ff2bd580995ab15205c47a81cc6d8efadf Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 5 Aug 2026 11:29:16 -0300 Subject: [PATCH 1/7] Add os_prober option to bootloader configuration model Introduce an os_prober flag on BootloaderConfiguration, defaulting to false to match the upstream GRUB default of shipping os-prober disabled. Bootloader.has_os_prober_support() limits the option to GRUB, the only supported bootloader that consumes os-prober output. --- archinstall/lib/models/bootloader.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index a4900e5bb6..65ca15838b 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -26,6 +26,9 @@ def has_removable_support(self) -> bool: case _: return False + def has_os_prober_support(self) -> bool: + return self == Bootloader.Grub + def is_uefi_only(self) -> bool: match self: case Bootloader.Systemd | Bootloader.Efistub | Bootloader.Refind: @@ -94,10 +97,11 @@ class BootloaderConfiguration(SubConfig): uki: bool = False removable: bool = True plymouth: PlymouthTheme | None = None + os_prober: bool = False @override def json(self) -> dict[str, Any]: - data = {'bootloader': self.bootloader.json(), 'uki': self.uki, 'removable': self.removable} + data = {'bootloader': self.bootloader.json(), 'uki': self.uki, 'removable': self.removable, 'os_prober': self.os_prober} if self.plymouth is not None: data['plymouth'] = self.plymouth.value @@ -111,6 +115,8 @@ def summary(self) -> list[str]: out.append(tr('UKI enabled')) if self.removable: out.append(tr('Removable')) + if self.os_prober: + out.append(tr('os-prober enabled')) if self.plymouth is not None: out.append(tr('Plymouth "{}"').format(self.plymouth.value)) @@ -122,7 +128,8 @@ def parse_arg(cls, config: dict[str, Any], skip_boot: bool) -> Self: uki = config.get('uki', False) removable = config.get('removable', True) plymouth = PlymouthTheme.from_arg(config.get('plymouth', None)) - return cls(bootloader=bootloader, uki=uki, removable=removable, plymouth=plymouth) + os_prober = config.get('os_prober', False) + return cls(bootloader=bootloader, uki=uki, removable=removable, plymouth=plymouth, os_prober=os_prober) @classmethod def get_default(cls, uefi: bool, skip_boot: bool = False) -> Self: @@ -130,7 +137,8 @@ def get_default(cls, uefi: bool, skip_boot: bool = False) -> Self: removable = uefi and bootloader.has_removable_support() uki = uefi and bootloader.has_uki_support() plymouth = None - return cls(bootloader=bootloader, uki=uki, removable=removable, plymouth=plymouth) + os_prober = False + return cls(bootloader=bootloader, uki=uki, removable=removable, plymouth=plymouth, os_prober=os_prober) def preview(self, uefi: bool) -> str: text = f'{tr("Bootloader")}: {self.bootloader.value}' @@ -149,6 +157,13 @@ def preview(self, uefi: bool) -> str: removable_string = tr('Disabled') text += f'{tr("Removable")}: {removable_string}' text += '\n' + if self.bootloader.has_os_prober_support(): + if self.os_prober: + os_prober_string = tr('Enabled') + else: + os_prober_string = tr('Disabled') + text += f'os-prober: {os_prober_string}' + text += '\n' if self.plymouth is not None: text += f'{tr("Plymouth")}: {self.plymouth.value}' text += '\n' From d5b701347b221b1ef2bba3e343ec5954d7288c1f Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 5 Aug 2026 11:29:20 -0300 Subject: [PATCH 2/7] Enable os-prober during GRUB installation When the os_prober option is set, install the os-prober package and set GRUB_DISABLE_OS_PROBER=false in /etc/default/grub so that grub-mkconfig detects other operating systems such as Windows. The edit runs for both the UKI and non-UKI paths and handles configs where the option is commented out, set to another value or missing entirely. Like the uki and removable options, an unsupported combination is downgraded with a warning instead of failing the installation. --- archinstall/lib/installer.py | 37 +++++++++++++++++++++++++++++++++-- archinstall/scripts/guided.py | 1 + 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index aeb5c6aea1..fc4e2594c8 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1331,6 +1331,7 @@ def _add_grub_bootloader( efi_partition: PartitionModification | None, uki_enabled: bool = False, bootloader_removable: bool = False, + os_prober: bool = False, ) -> None: debug('Installing grub bootloader') @@ -1430,6 +1431,27 @@ def _add_grub_bootloader( grub_default.write_text(config) + if os_prober: + self.pacman.strap('os-prober') + + # grub-mkconfig only runs os-prober when GRUB_DISABLE_OS_PROBER is + # explicitly set to false; the stock config ships the option commented out + grub_default = self.target / 'etc/default/grub' + config = grub_default.read_text() + + config, count = re.subn( + r'^#?GRUB_DISABLE_OS_PROBER=.*$', + 'GRUB_DISABLE_OS_PROBER=false', + config, + count=1, + flags=re.MULTILINE, + ) + + if count == 0: + config += '\nGRUB_DISABLE_OS_PROBER=false\n' + + grub_default.write_text(config) + try: self.arch_chroot( f'grub-mkconfig -o {boot_dir}/grub/grub.cfg', @@ -1833,7 +1855,12 @@ def _config_uki( error('Error generating initramfs (continuing anyway)') def add_bootloader( - self, bootloader: Bootloader, uki_enabled: bool = False, bootloader_removable: bool = False, plymouth: PlymouthTheme | None = None + self, + bootloader: Bootloader, + uki_enabled: bool = False, + bootloader_removable: bool = False, + plymouth: PlymouthTheme | None = None, + os_prober: bool = False, ) -> None: """ Adds a bootloader to the installation instance. @@ -1848,6 +1875,7 @@ def add_bootloader( :param uki_enabled: Whether to use unified kernel images :param bootloader_removable: Whether to install to removable media location (UEFI only, for GRUB and Limine) :param plymouth: Optional Plymouth theme to install and configure + :param os_prober: Whether to enable os-prober so grub-mkconfig detects other operating systems (GRUB only) """ for plugin in plugins.values(): @@ -1883,6 +1911,11 @@ def add_bootloader( warn(f'Bootloader {bootloader.value} lacks removable support; disabling.') bootloader_removable = False + # validate os-prober option + if os_prober and not bootloader.has_os_prober_support(): + warn(f'Bootloader {bootloader.value} does not support os-prober; disabling.') + os_prober = False + if plymouth is not None: self._install_plymouth(plymouth) @@ -1899,7 +1932,7 @@ def add_bootloader( case Bootloader.Systemd: self._add_systemd_bootloader(boot_partition, root, efi_partition, uki_enabled) case Bootloader.Grub: - self._add_grub_bootloader(boot_partition, root, efi_partition, uki_enabled, bootloader_removable) + self._add_grub_bootloader(boot_partition, root, efi_partition, uki_enabled, bootloader_removable, os_prober) case Bootloader.Efistub: self._add_efistub_bootloader(boot_partition, root, uki_enabled) case Bootloader.Limine: diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 627a3b7553..9e5371c801 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -120,6 +120,7 @@ def perform_installation( config.bootloader_config.uki, config.bootloader_config.removable, config.bootloader_config.plymouth, + config.bootloader_config.os_prober, ) if config.network_config: From 864780da662aacd08e09d603fb9b526d13c076d1 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 5 Aug 2026 11:29:23 -0300 Subject: [PATCH 3/7] Add os-prober toggle to the bootloader menu Expose the os_prober option in the bootloader submenu. The item is only enabled when GRUB is selected and is reset when switching to a bootloader without os-prober support, mirroring the uki and removable handling. Regenerate the translation template for the new strings. --- archinstall/lib/bootloader/bootloader_menu.py | 42 +++++++++++++++++++ archinstall/locales/base.pot | 29 +++++++++---- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/archinstall/lib/bootloader/bootloader_menu.py b/archinstall/lib/bootloader/bootloader_menu.py index 88fe8f7343..65f28add95 100644 --- a/archinstall/lib/bootloader/bootloader_menu.py +++ b/archinstall/lib/bootloader/bootloader_menu.py @@ -41,6 +41,11 @@ def _define_menu_options(self) -> list[MenuItem]: if not removable_enabled: self._bootloader_conf.removable = False + # os-prober availability + os_prober_enabled = bootloader.has_os_prober_support() + if not os_prober_enabled: + self._bootloader_conf.os_prober = False + return [ MenuItem( text=tr('Bootloader'), @@ -66,6 +71,14 @@ def _define_menu_options(self) -> list[MenuItem]: key='removable', enabled=removable_enabled, ), + MenuItem( + text=tr('Detect other operating systems'), + action=self._select_os_prober, + value=self._bootloader_conf.os_prober, + preview_action=self._prev_os_prober, + key='os_prober', + enabled=os_prober_enabled, + ), MenuItem( text=tr('Plymouth'), action=self._select_plymouth, @@ -92,6 +105,13 @@ def _prev_removable(self, item: MenuItem) -> str | None: return tr('Will install to /EFI/BOOT/ (removable location, safe default)') return tr('Will install to custom location with NVRAM entry') + def _prev_os_prober(self, item: MenuItem) -> str | None: + os_prober_text = f'{tr("Detect other operating systems")}' + if item.value: + return f'{os_prober_text}: {tr("Enabled")}' + else: + return f'{os_prober_text}: {tr("Disabled")}' + def _prev_plymouth(self, item: MenuItem) -> str | None: if item.value: return f'{tr("Plymouth")}: {item.value.value}' @@ -127,6 +147,15 @@ async def _select_bootloader(self, preset: Bootloader | None) -> Bootloader | No self._bootloader_conf.removable = True removable_item.enabled = True + # Update os-prober option based on bootloader + os_prober_item = self._menu_item_group.find_by_key('os_prober') + if not bootloader.has_os_prober_support(): + os_prober_item.enabled = False + os_prober_item.value = False + self._bootloader_conf.os_prober = False + else: + os_prober_item.enabled = True + return bootloader async def _select_plymouth(self, preset: PlymouthTheme | None) -> PlymouthTheme | None: @@ -219,6 +248,19 @@ async def _select_removable(self, preset: bool) -> bool: case ResultType.Reset: raise ValueError('Unhandled result type') + async def _select_os_prober(self, preset: bool) -> bool: + prompt = tr('Would you like to enable os-prober to detect other operating systems (e.g. Windows)?') + '\n' + + result = await Confirmation(header=prompt, allow_skip=True, preset=preset).show() + + match result.type_: + case ResultType.Skip: + return preset + case ResultType.Selection: + return result.item() == MenuItem.yes() + case ResultType.Reset: + raise ValueError('Unhandled result type') + async def select_bootloader( preset: Bootloader | None, diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index a242cbaf24..b5ee0d7f90 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -209,6 +209,9 @@ msgstr "" msgid "Install to removable location" msgstr "" +msgid "Detect other operating systems" +msgstr "" + msgid "Plymouth" msgstr "" @@ -259,6 +262,11 @@ msgstr "" msgid "Systems where you want the disk to be bootable on any computer." msgstr "" +msgid "" +"Would you like to enable os-prober to detect other operating systems (e.g. " +"Windows)?" +msgstr "" + msgid "Select bootloader to install" msgstr "" @@ -906,6 +914,9 @@ msgstr "" msgid "Removable" msgstr "" +msgid "os-prober enabled" +msgstr "" + #, python-brace-format msgid "Plymouth \"{}\"" msgstr "" @@ -1122,6 +1133,15 @@ msgid "" "Select any packages from the below list that should be installed additionally" msgstr "" +msgid "" +"Pacman is already running, waiting maximum 10 minutes for it to terminate." +msgstr "" + +msgid "" +"Pre-existing pacman lock never exited. Please clean up any existing pacman " +"sessions before using archinstall." +msgstr "" + #, python-brace-format msgid "Enter the number of parallel downloads (1-{})" msgstr "" @@ -1133,15 +1153,6 @@ msgstr "" msgid "Enable colored output for pacman" msgstr "" -msgid "" -"Pacman is already running, waiting maximum 10 minutes for it to terminate." -msgstr "" - -msgid "" -"Pre-existing pacman lock never exited. Please clean up any existing pacman " -"sessions before using archinstall." -msgstr "" - msgid "The proprietary Nvidia driver is not supported by Sway." msgstr "" From 702e7528d7d477ceb7cb47711369f30c48948184 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 5 Aug 2026 11:29:25 -0300 Subject: [PATCH 4/7] Update tests, docs and samples for the os_prober option Cover os_prober in the config fixture and parsing assertions, and document the new key in the config options table and sample configs. --- docs/cli_parameters/config/config_options.csv | 2 +- docs/installing/guided.rst | 3 ++- examples/config-sample.json | 3 ++- tests/data/test_config.json | 3 ++- tests/test_args.py | 1 + 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/cli_parameters/config/config_options.csv b/docs/cli_parameters/config/config_options.csv index a902d1d20e..bb3ce2ebba 100644 --- a/docs/cli_parameters/config/config_options.csv +++ b/docs/cli_parameters/config/config_options.csv @@ -2,7 +2,7 @@ Key,Value(s),Description,Required additional-repositories,[ `multilib `_!, `testing `_ ],Enables one or more of the testing and multilib repositories before proceeding with installation,No archinstall-language,`lang `__,Sets the TUI language used *(make sure to use the ``lang`` value not the ``abbr``)*,No audio_config,`pipewire `_!, `pulseaudio `_,Audioserver to be installed,No -bootloader_config,"{ bootloader: `Systemd-boot `_!, `grub `_!, `limine `_!, uki: ``true``/``false``!, removable: ``true``/``false`` }","Bootloader configuration. ``bootloader`` selects which bootloader to install *(grub/limine mandatory on BIOS)*. ``uki`` enables unified kernel images *(UEFI only!, systemd-boot/limine only)*. ``removable`` installs to default removable media path /EFI/BOOT/ instead of NVRAM *(UEFI only!, grub/limine only)*",Yes +bootloader_config,"{ bootloader: `Systemd-boot `_!, `grub `_!, `limine `_!, uki: ``true``/``false``!, removable: ``true``/``false``!, os_prober: ``true``/``false`` }","Bootloader configuration. ``bootloader`` selects which bootloader to install *(grub/limine mandatory on BIOS)*. ``uki`` enables unified kernel images *(UEFI only!, systemd-boot/limine only)*. ``removable`` installs to default removable media path /EFI/BOOT/ instead of NVRAM *(UEFI only!, grub/limine only)*. ``os_prober`` installs os-prober so GRUB can detect other operating systems such as Windows *(grub only)*",Yes debug,``true``!, ``false``,Enables debug output,No disk_config,*Read more under* :ref:`disk config`,Contains the desired disk setup to be used during installation,No disk_encryption,*Read more about under* :ref:`disk encryption`,Parameters for disk encryption applied on top of ``disk_config``,No diff --git a/docs/installing/guided.rst b/docs/installing/guided.rst index 562d2b2980..2b55c42ed9 100644 --- a/docs/installing/guided.rst +++ b/docs/installing/guided.rst @@ -69,7 +69,8 @@ The contents of :code:`https://domain.lan/config.json`: "bootloader_config": { "bootloader": "Systemd-boot", "uki": false, - "removable": false + "removable": false, + "os_prober": false }, "bootloader": "Systemd-boot", "debug": false, diff --git a/examples/config-sample.json b/examples/config-sample.json index ac366b6c9f..47d8da535f 100644 --- a/examples/config-sample.json +++ b/examples/config-sample.json @@ -6,7 +6,8 @@ "bootloader_config": { "bootloader": "Systemd-boot", "uki": false, - "removable": false + "removable": false, + "os_prober": false }, "debug": false, "disk_config": { diff --git a/tests/data/test_config.json b/tests/data/test_config.json index 618bd8e9c7..a2f01aaed3 100644 --- a/tests/data/test_config.json +++ b/tests/data/test_config.json @@ -24,7 +24,8 @@ "bootloader_config": { "bootloader": "Systemd-boot", "uki": false, - "removable": false + "removable": false, + "os_prober": true }, "services": [ "service_1", diff --git a/tests/test_args.py b/tests/test_args.py index 324f5a1173..7c6f3d8bdd 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -232,6 +232,7 @@ def test_config_file_parsing( bootloader=Bootloader.Systemd, uki=False, removable=False, + os_prober=True, ), hostname='archy', kernels=['linux-zen'], From 5edf382dd35d3e740f53996d11367904c590d6b2 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 5 Aug 2026 11:48:33 -0300 Subject: [PATCH 5/7] Install fuse3 alongside os-prober Since version 1.77 os-prober probes unmounted partitions exclusively through grub-mount and silently skips them when it cannot run. The grub package ships grub-mount but only lists fuse3 as an optional dependency, so without it os-prober is limited to already-mounted partitions and misses the common dual-boot case of Windows living on another disk. Installing fuse3 also lets grub-mount read NTFS in userspace, keeping detection independent of kernel filesystem modules inside the installation chroot. --- archinstall/lib/installer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index fc4e2594c8..f4dd8b2531 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1432,7 +1432,9 @@ def _add_grub_bootloader( grub_default.write_text(config) if os_prober: - self.pacman.strap('os-prober') + # fuse3 enables grub-mount, which os-prober requires to inspect + # partitions that are not mounted (e.g. Windows on another disk) + self.pacman.strap(['os-prober', 'fuse3']) # grub-mkconfig only runs os-prober when GRUB_DISABLE_OS_PROBER is # explicitly set to false; the stock config ships the option commented out From 54f993c5128d940288965b4b73f9708060b1c128 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 5 Aug 2026 14:58:38 -0300 Subject: [PATCH 6/7] Log os-prober enablement during GRUB setup The os-prober block straps packages and edits /etc/default/grub without leaving any trace in the install log. Add a debug marker, matching the logging convention of the surrounding bootloader steps. --- archinstall/lib/installer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index f4dd8b2531..1adbcef4ad 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1432,6 +1432,8 @@ def _add_grub_bootloader( grub_default.write_text(config) if os_prober: + debug('Enabling os-prober in GRUB configuration') + # fuse3 enables grub-mount, which os-prober requires to inspect # partitions that are not mounted (e.g. Windows on another disk) self.pacman.strap(['os-prober', 'fuse3']) From f5c9a00ee20add329a424c8482274eba1d59c091 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 5 Aug 2026 19:29:56 -0300 Subject: [PATCH 7/7] Translate the os-prober label in the bootloader preview Reuse the existing 'Detect other operating systems' msgid so the preview matches the menu item label and stays localizable, like the neighbouring Removable and Plymouth labels. Suggested in PR review. --- archinstall/lib/models/bootloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index 65ca15838b..84cc999278 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -162,7 +162,7 @@ def preview(self, uefi: bool) -> str: os_prober_string = tr('Enabled') else: os_prober_string = tr('Disabled') - text += f'os-prober: {os_prober_string}' + text += f'{tr("Detect other operating systems")}: {os_prober_string}' text += '\n' if self.plymouth is not None: text += f'{tr("Plymouth")}: {self.plymouth.value}'