fixed windows module

This commit is contained in:
Sohrab Behdani 2025-01-25 12:15:53 +03:30
parent e663c5ee91
commit 7a959951e9
3 changed files with 158 additions and 216 deletions

View file

@ -101,63 +101,5 @@ Column {
}
}
}
Rectangle {
width: 700
height: 150
color: "#1b1e20"
radius: 5
border.width: 1
border.color: "#646b75"
Text {
width: 600
height: 104
anchors.centerIn: parent
text: qsTr("<strong>No Bootloader</strong><br><br>Selecting no bootloader might result in an <strong>un-bootable system</strong>,<br>If you don't already have a bootloader that you can add this install to.")
font.pointSize: 10
color: "#ffffff"
anchors.verticalCenterOffset: 0
anchors.horizontalCenterOffset: -20.0
wrapMode: Text.WordWrap
}
Switch {
id: element3
x: 500
y: 110
width: 187
height: 14
text: qsTr("No bootloader")
checked: false
hoverEnabled: true
ButtonGroup.group: switchGroup
indicator: Rectangle {
implicitWidth: 40
implicitHeight: 14
radius: 10
color: element3.checked ? "#3498db" : "#B9B9B9"
border.color: element3.checked ? "#3498db" : "#cccccc"
Rectangle {
x: element3.checked ? parent.width - width : 0
y: (parent.height - height) / 2
width: 20
height: 20
radius: 10
color: element3.down ? "#cccccc" : "#ffffff"
border.color: element3.checked ? (element3.down ? "#3498db" : "#3498db") : "#999999"
}
}
onCheckedChanged: {
if (! checked) {
print("no btl not checked")
} else {
print("no bootloader")
config.packageChoice = "none"
}
}
}
}
}

View file

@ -50,7 +50,7 @@ Column {
width: 600
height: 90
anchors.centerIn: parent
text: qsTr("<strong>Systemd-boot</strong><br><br>provides a simple experience<br>which will work for most circumstances.<br>This is the default option for <strong>EndeavourOS</strong>.")
text: qsTr("<strong>Systemd-boot</strong><br><br>provides a simple experience<br>which will work for most circumstances.<br>This is the default option for <strong>Parch Linux</strong>.")
font.pointSize: 10
color: "#ffffff"
anchors.verticalCenterOffset: 0

View file

@ -1,203 +1,203 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-License-Identifier: GPL-3.0-or-later
#
import fileinput
import logging
import os
import shutil
import subprocess
import stat
import tempfile
from contextlib import contextmanager
import libcalamares
from libcalamares.utils import check_target_env_call, check_target_env_output, gettext_path, gettext_languages
from libcalamares.utils import host_env_process_output
from libcalamares.utils import check_target_env_call
from libcalamares.utils import check_target_env_output
_ = libcalamares.utils.gettext_translation("calamares-python",
localedir=gettext_path(),
languages=gettext_languages(),
fallback=True).gettext
import gettext
logger = logging.getLogger(__name__)
_ = gettext.translation("calamares-python",
localedir=libcalamares.utils.gettext_path(),
languages=libcalamares.utils.gettext_languages(),
fallback=True).gettext
def pretty_name():
return _("Configure Windows Boot Entries")
return _("Install bootloader.")
# --- Error Classes ---
class WindowsBootEntryError(Exception):
"""Base exception for Windows boot entry errors"""
pass
def copytree(source_dir, dest_dir):
"""
Recursively copy source_dir to dest_dir. This is basically a work-around for the fact that native Python copytree
can't handle the situation where dest_dir already exists
"""
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
for item in os.listdir(source_dir):
s = os.path.join(source_dir, item)
d = os.path.join(dest_dir, item)
if os.path.isdir(s):
copytree(s, d)
else:
shutil.copy2(s, d)
class MissingGlobalStorageError(WindowsBootEntryError):
"""Raised when required global storage keys are missing"""
pass
def handle_systemdboot(efi_directory):
"""
For systemd-boot, we copy the Microsoft EFI entry to the EFI partition where systemd-boot is installed
"""
class BootloaderConfigError(WindowsBootEntryError):
"""Raised for bootloader configuration errors"""
pass
# --- Helper Functions ---
@contextmanager
def mounted(device, mountpoint):
"""Context manager for temporary mounting"""
# Get the root mount point and build the full path to the mounted ESP
try:
host_env_process_output(["mount", device, mountpoint])
yield mountpoint
except subprocess.CalledProcessError as e:
logger.error("Failed to mount %s at %s: %s", device, mountpoint, e)
raise BootloaderConfigError(f"Mount failed: {e}") from e
finally:
installation_root_path = libcalamares.globalstorage.value("rootMountPoint")
except KeyError:
libcalamares.utils.warning('Global storage value "rootMountPoint" missing')
install_efi_directory = installation_root_path + efi_directory
# espList holds the list of EFI partitions from the partition module
try:
efi_partition_list = libcalamares.globalstorage.value("espList")
except KeyError:
libcalamares.utils.warning("No ESP list in global storage")
return None
# Get the partitions from global storage
try:
partitions = libcalamares.globalstorage.value("partitions")
except KeyError:
libcalamares.utils.warning("partitions missing from global storage")
return None
# Iterate over the partitions and search for an EFI partition containing the Microsoft data
for partition in partitions:
try:
host_env_process_output(["umount", mountpoint])
except subprocess.CalledProcessError as e:
logger.warning("Failed to unmount %s: %s", mountpoint, e)
if partition["device"] in efi_partition_list and partition["mountPoint"] != efi_directory:
libcalamares.utils.debug(f"Found foreign efi at {partition['device']}")
temp_path = tempfile.mkdtemp()
libcalamares.utils.debug(f"Attempting to mount {partition['device'].strip()} at {temp_path}")
libcalamares.utils.host_env_process_output(["mount", partition["device"].strip(), temp_path])
source_path = os.path.join(temp_path, "EFI", "Microsoft")
if os.path.isdir(source_path):
target_path = os.path.join(install_efi_directory, "EFI", "Microsoft")
copytree(source_path, target_path)
libcalamares.utils.host_env_process_output(["umount", temp_path])
except KeyError:
pass
except subprocess.CalledProcessError:
libcalamares.utils.warning("Failed to mount foreign EFI dir")
pass
except shutil.Error:
libcalamares.utils.warning("Failed to copy Windows files from foreign EFI dir")
pass
def validate_global_storage():
"""Validate required global storage values exist"""
required_keys = [
"rootMountPoint",
"partitions",
"efiSystemPartition",
"firmwareType"
]
missing = [k for k in required_keys if not libcalamares.globalstorage.contains(k)]
if missing:
raise MissingGlobalStorageError(
f"Missing required global storage keys: {', '.join(missing)}"
)
def enable_osprober(installation_root_path, enable):
"""
Enabled or disabled os-prober in the target based on the value of the enable parameter
"""
# Update the config in the file to enable or disable os-prober
for line in fileinput.input(os.path.join(installation_root_path, "etc/default/grub"), inplace=True):
line = line.strip()
if line.startswith("#GRUB_DISABLE_OS_PROBER=false") and enable:
print(line.lstrip("#"))
elif line.startswith("GRUB_DISABLE_OS_PROBER=false") and not enable:
print("#" + line)
else:
print(line)
# --- Bootloader Handlers ---
def handle_systemd_boot(efi_directory):
"""Handle Windows entries for systemd-boot"""
root_path = libcalamares.globalstorage.value("rootMountPoint")
install_efi = os.path.join(root_path, efi_directory.lstrip('/'))
def write_grub_config(installation_root_path, filename, entry):
"""
Creates a custom grub entry with the data found in "entry" with the name "filename"
"""
# Get all EFI partitions from partition module
esp_list = libcalamares.globalstorage.value("espList", [])
partitions = libcalamares.globalstorage.value("partitions")
conf_path = os.path.join(installation_root_path, "etc", "grub.d", filename)
with open(conf_path, 'w') as conf_file:
conf_file.write("#!/bin/sh\n")
conf_file.write("exec tail -n +3 $0\n\n")
conf_file.writelines(entry)
for part in partitions:
try:
if part["device"] in esp_list and part["mountPoint"] != efi_directory:
logger.debug("Found foreign ESP at %s", part["device"])
with tempfile.TemporaryDirectory() as temp_mount:
try:
with mounted(part["device"].strip(), temp_mount):
source_dir = os.path.join(temp_mount, "EFI", "Microsoft")
if os.path.isdir(source_dir):
target_dir = os.path.join(install_efi, "EFI", "Microsoft")
logger.info("Copying Windows boot files from %s to %s", source_dir, target_dir)
shutil.copytree(source_dir, target_dir, dirs_exist_ok=True)
except BootloaderConfigError:
continue # Already logged, continue with other partitions
except KeyError as e:
logger.warning("Partition missing required key: %s", e)
try:
os.chmod(conf_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
except os.error as e:
libcalamares.utils.warning(f"Failed to chmod grub config, the error was: {e}")
return None
def handle_grub():
"""Handle Windows entries for GRUB"""
root_path = libcalamares.globalstorage.value("rootMountPoint")
grub_config = os.path.join(root_path, "etc/default/grub")
"""
Enables os-prober and checks for a windows entry. If it finds one, it is written to /etc/grub.d/45_eos_windows.
"""
# Enable os-prober temporarily
# Get the root mount point
try:
with open(grub_config, 'r+') as f:
content = f.read()
f.seek(0)
f.write(content.replace("GRUB_DISABLE_OS_PROBER=true", "#GRUB_DISABLE_OS_PROBER=false"))
f.truncate()
except IOError as e:
raise BootloaderConfigError(f"Failed to modify GRUB config: {e}") from e
installation_root_path = libcalamares.globalstorage.value("rootMountPoint")
except KeyError:
libcalamares.utils.warning('Global storage value "rootMountPoint" missing')
return None
# Generate GRUB config
try:
output = check_target_env_output(["grub-mkconfig"])
except subprocess.CalledProcessError as e:
raise BootloaderConfigError(f"GRUB config generation failed: {e}") from e
enable_osprober(installation_root_path, True)
# Parse Windows entries
windows_entry = []
in_os_prober = False
for line in output.split('\n'):
if "### BEGIN /etc/grub.d/30_os-prober ###" in line:
in_os_prober = True
elif "### END /etc/grub.d/30_os-prober ###" in line:
break
elif in_os_prober and "Windows" in line:
windows_entry.append(line + '\n')
mkconfig_output = check_target_env_output(["grub-mkconfig"])
if windows_entry:
entry_path = os.path.join(root_path, "etc/grub.d/45_parch_windows")
try:
with open(entry_path, 'w') as f:
f.write("#!/bin/sh\n")
f.write("exec tail -n +3 $0\n\n")
f.writelines(windows_entry)
os.chmod(entry_path, 0o755)
except IOError as e:
raise BootloaderConfigError(f"Failed to write Windows GRUB entry: {e}") from e
# Find the contents of the entry
found_osprober = False
found_windows = False
for line in mkconfig_output.splitlines():
if line.strip() == "### BEGIN /etc/grub.d/30_os-prober ###":
found_osprober = True
elif "menuentry" in line and "Windows" in line and found_osprober:
found_windows = True
entry = [f"{line}\n"]
elif found_windows and found_osprober:
entry.append(f"{line}\n")
if line.strip().startswith("}"):
break
# Disable os-prober
try:
with open(grub_config, 'r+') as f:
content = f.read()
f.seek(0)
f.write(content.replace("#GRUB_DISABLE_OS_PROBER=false", "GRUB_DISABLE_OS_PROBER=true"))
f.truncate()
except IOError as e:
logger.warning("Failed to restore GRUB os-prober setting: %s", e)
if found_windows:
write_grub_config(installation_root_path, "45_eos_windows", entry)
else:
libcalamares.utils.debug("No Windows entry found by os-prober")
# We are done, disable os-prober
enable_osprober(installation_root_path, False)
return None
# --- Main Execution ---
def run():
"""Main entry point for the module"""
fw_type = libcalamares.globalstorage.value("firmwareType")
partitions = libcalamares.globalstorage.value("partitions")
efi_directory = libcalamares.globalstorage.value("efiSystemPartition")
if fw_type == "efi":
esp_found = [p for p in partitions if p["mountPoint"] == efi_directory]
if not esp_found:
libcalamares.utils.warning(f"EFI system, but nothing mounted on {efi_directory}")
return None
# Get the boot loader selection from global storage if it is set in the config file
try:
validate_global_storage()
gs_name = libcalamares.job.configuration["bootLoaderVar"]
boot_loader = libcalamares.globalstorage.value(gs_name)
except KeyError:
libcalamares.utils.warning(
f"Specified global storage value not found in global storage")
return None
# Get configuration values
config = libcalamares.job.configuration
bootloader_var = config.get("bootLoaderVar", "bootloader")
bootloader = libcalamares.globalstorage.value(bootloader_var)
# If the user has selected not to install bootloader, bail out here
if boot_loader.casefold() == "none":
libcalamares.utils.debug("No bootloader selecting, skipping Windows boot entry creation")
return None
if not bootloader:
logger.warning("No bootloader specified in global storage")
return None
if bootloader.lower() == "none":
logger.info("Skipping bootloader configuration (user selected 'none')")
return None
# Get EFI directory
efi_dir = libcalamares.globalstorage.value("efiSystemPartition", "/boot/efi")
# Dispatch to handler
if bootloader == "systemd-boot":
handle_systemd_boot(efi_dir)
elif bootloader == "grub":
handle_grub()
else:
logger.warning("Unsupported bootloader: %s", bootloader)
except MissingGlobalStorageError as e:
logger.error("Configuration error: %s", e)
return _("Missing system configuration - cannot configure boot entries.")
except BootloaderConfigError as e:
logger.error("Boot configuration failed: %s", e)
return _("Failed to configure boot entries for Windows.")
except Exception as e:
logger.exception("Unexpected error during Windows boot configuration")
return _("Critical error during boot configuration: {}").format(str(e))
if boot_loader == "systemd-boot":
handle_systemdboot(efi_directory)
elif boot_loader == "grub":
handle_grub()
else:
libcalamares.utils.debug(f"No manual Windows entry creation available for {boot_loader}")
return None