mirror of
https://github.com/parchlinux/calamares.git
synced 2025-02-23 02:15:44 -05:00
fixed windows module
This commit is contained in:
parent
e663c5ee91
commit
7a959951e9
3 changed files with 158 additions and 216 deletions
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue