277 lines
11 KiB
Diff
277 lines
11 KiB
Diff
|
From a1980ee725fc73e367bde3706c327684ac5e35ee Mon Sep 17 00:00:00 2001
|
||
|
From: Eric Duncan <me@eduncan911.com>
|
||
|
Date: Fri, 19 Oct 2018 08:02:13 +0200
|
||
|
Subject: [PATCH] anaconda: Fix macOS EFI Installation
|
||
|
MIME-Version: 1.0
|
||
|
Content-Type: text/plain; charset=UTF-8
|
||
|
Content-Transfer-Encoding: 8bit
|
||
|
|
||
|
Typical GRUB2 installations would execute the script
|
||
|
located at /usr/libexec/mactel-boot-setup which would
|
||
|
modify the HFS+ ESP files and bless the specified efi.
|
||
|
However, we are not using GRUB at this time which would
|
||
|
cause that script to exit earlier.
|
||
|
|
||
|
These changes will execute the relevant commands
|
||
|
to symlink the efi file in the /System directory as well
|
||
|
the cfg file. Lastly, macOS requires the bootable efi
|
||
|
file to be blessed.
|
||
|
|
||
|
We also attempt to place some user-friendly icons
|
||
|
for Qubes to show to the user.
|
||
|
|
||
|
Lastly, we add a README with some instructions on how
|
||
|
to get into rescue mode from macOS.
|
||
|
|
||
|
Signed-off-by: Frédéric Pierret <frederic.epitre@orange.fr>
|
||
|
---
|
||
|
pyanaconda/bootloader.py | 220 +++++++++++++++++++++++++++++++++++++++++++++--
|
||
|
1 file changed, 215 insertions(+), 5 deletions(-)
|
||
|
|
||
|
diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
|
||
|
index ad8e8c2e7..821eb2100 100644
|
||
|
--- a/pyanaconda/bootloader.py
|
||
|
+++ b/pyanaconda/bootloader.py
|
||
|
@@ -1908,16 +1908,208 @@ class XenEFI(EFIGRUB):
|
||
|
write_config = BootLoader.write_config
|
||
|
|
||
|
|
||
|
-class MacEFIGRUB(EFIGRUB):
|
||
|
+class MacEFIGRUB(XenEFI):
|
||
|
+ """Special EFI handling for macOS HFS+ ESP partition.
|
||
|
+
|
||
|
+ Typical GRUB2 installations would execute the script
|
||
|
+ located at /usr/libexec/mactel-boot-setup which would
|
||
|
+ modify the HFS+ ESP files and bless the specified efi.
|
||
|
+
|
||
|
+ However, we are not using GRUB at this time which would
|
||
|
+ cause that script to exit earlier.
|
||
|
+
|
||
|
+ In this class, we will execute the relevant commands
|
||
|
+ to symlink the efi file in the /System directory as well
|
||
|
+ the cfg file. Lastly, macOS requires the bootable efi
|
||
|
+ file to be blessed.
|
||
|
+ """
|
||
|
+
|
||
|
+ def __init__(self):
|
||
|
+ super(MacEFIGRUB, self).__init__()
|
||
|
+ self._mountpoint = "/boot/efi" # fixme: extract from writeBootLoader()
|
||
|
+ self._system_label = "Qubes OS"
|
||
|
+ self._mactel_sys_dir = "{}/{}".format(self._mountpoint, "/System/Library/Coreservices")
|
||
|
+ self._mactel_artwork_dir = "{}/{}".format("/usr/share/pixmaps/bootloader/apple", self.efi_dir)
|
||
|
+
|
||
|
def mactel_config(self):
|
||
|
- if os.path.exists(iutil.getSysroot() + "/usr/libexec/mactel-boot-setup"):
|
||
|
- rc = iutil.execInSysroot("/usr/libexec/mactel-boot-setup", [])
|
||
|
- if rc:
|
||
|
- log.error("failed to configure Mac boot loader")
|
||
|
+ """Modifies the HFS+ ESP partition to be bootable.
|
||
|
+
|
||
|
+ Based on the /usr/libexec/mactel-boot-setup script,
|
||
|
+ we will create two symlinks pointing to the Qubes
|
||
|
+ Xen version that is installed and configured for
|
||
|
+ the system. We also need to run hfs-bless on
|
||
|
+ the specific EFI file we allow to be bootable.
|
||
|
+ """
|
||
|
+
|
||
|
+ log.info("mactel configure MacEFI boot partition")
|
||
|
+
|
||
|
+ not_ready = False
|
||
|
+ xen_efi_file = "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.efi")
|
||
|
+ if not os.path.exists(xen_efi_file):
|
||
|
+ log.waring("mactel efi file not found: %s", xen_efi_file)
|
||
|
+ not_ready = True
|
||
|
+ xen_cfg_file = "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.cfg")
|
||
|
+ if not os.path.exists(xen_cfg_file):
|
||
|
+ log.warning("mactel efi cfg not found: %s", xen_cfg_file)
|
||
|
+ not_ready = True
|
||
|
+ if not_ready:
|
||
|
+ log.error("mactel cannot continue with mactel_config. see log for details.")
|
||
|
+ return
|
||
|
+
|
||
|
+ sys_dir = iutil.getSysroot() + self._mactel_sys_dir
|
||
|
+ if not os.path.exists(sys_dir):
|
||
|
+ try:
|
||
|
+ os.makedirs(sys_dir)
|
||
|
+ except Exception as error:
|
||
|
+ log.warning("mactel tried to create sys_dir %s but received error: %s", sys_dir, error)
|
||
|
+
|
||
|
+ src_efi = "{}/{}".format("../../../EFI/" + self.efi_dir, "xen.efi")
|
||
|
+ sys_efi = "{}/{}".format(sys_dir, "boot.efi")
|
||
|
+ self._symlink(src_efi, sys_efi)
|
||
|
+
|
||
|
+ src_cfg = "{}/{}".format("../../../EFI/" + self.efi_dir, "xen.cfg")
|
||
|
+ sys_cfg = "{}/{}".format(sys_dir, "xen.cfg") # convention for Xen's cfg lookup
|
||
|
+ self._symlink(src_cfg, sys_cfg)
|
||
|
+
|
||
|
+ result_code = iutil.execInSysroot("touch", ["{}/{}".format(self._mountpoint, "mach_kernel")])
|
||
|
+ if result_code:
|
||
|
+ log.error("mactel failed to touch: %s", "{}/{}".format(self._mountpoint, "mach_kernel"))
|
||
|
+
|
||
|
+ text = """<?xml version="1.0" encoding="UTF-8"?>
|
||
|
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||
|
+<plist version="1.0">
|
||
|
+<dict>
|
||
|
+ <key>ProductBuildVersion</key>
|
||
|
+ <string></string>
|
||
|
+ <key>ProductName</key>
|
||
|
+ <string>Linux</string>
|
||
|
+ <key>ProductVersion</key>
|
||
|
+ <string>{}</string>
|
||
|
+</dict>
|
||
|
+</plist>""".format(self._system_label)
|
||
|
+ sys_ver_file_name = "{}/{}".format(sys_dir, "SystemVersion.plist")
|
||
|
+ try:
|
||
|
+ with open(sys_ver_file_name, "w") as sys_version_file:
|
||
|
+ sys_version_file.write(text)
|
||
|
+ except IOError as error:
|
||
|
+ log.error("mactel failed to open %s for write: %s", sys_ver_file_name, error)
|
||
|
+ cfg_ver_file_name = "{}/{}".format(iutil.getSysroot() + self.config_dir, "SystemVersion.plist")
|
||
|
+ self._copy_file(sys_ver_file_name, cfg_ver_file_name)
|
||
|
+
|
||
|
+ bless_file = "{}/{}".format(self.config_dir, "xen.efi")
|
||
|
+ result_code = iutil.execInSysroot("hfs-bless", [bless_file])
|
||
|
+ if result_code:
|
||
|
+ log.error("mactel failed to run 'hfs-bless %s'", bless_file)
|
||
|
+
|
||
|
+ # make the partition macOS friendly (e.g. to set rescue mode from macOS)
|
||
|
+ fseventsd = "{}/{}".format(iutil.getSysroot() + self._mountpoint, ".fseventsd")
|
||
|
+ try:
|
||
|
+ os.makedirs(fseventsd)
|
||
|
+ except Exception as error:
|
||
|
+ log.error("mactel could not make directory %s: %s", fseventsd, error)
|
||
|
+ touch_files = [
|
||
|
+ "{}/{}/{}".format(self._mountpoint, ".fseventsd", "no_log"),
|
||
|
+ "{}/{}".format(self._mountpoint, ".metadata_never_index"),
|
||
|
+ "{}/{}".format(self._mountpoint, ".Trashes")]
|
||
|
+ result_code = iutil.execInSysroot("touch", touch_files)
|
||
|
+ if result_code:
|
||
|
+ log.error("mactel failed to touch: %s", touch_files)
|
||
|
+
|
||
|
+ text = """Qubes OS
|
||
|
+
|
||
|
+CAUTION
|
||
|
+--
|
||
|
+This partition is used to boot Qubes OS on a macOS machine.
|
||
|
+Modifying the contents could render your installation unbootable.
|
||
|
+
|
||
|
+RESCUE / RECOVERY MODE
|
||
|
+--
|
||
|
+In the event that you need to boot into Rescue mode, you will need
|
||
|
+to change the default Xen configuration.
|
||
|
+
|
||
|
+0. Backup your xen.cfg.
|
||
|
+
|
||
|
+ cp /EFI/qubes/xen.cfg /EFI/qubes/xen.cfg~
|
||
|
+
|
||
|
+1. Open /EFI/qubes/xen.cfg
|
||
|
|
||
|
+2. Look at the sections with the named headers. One may already be
|
||
|
+ named "qubes-rescue", which we will use in Step 4 below.
|
||
|
+
|
||
|
+ If not, we will need to make one. Copy your current configuration
|
||
|
+ to another entry. But change the kernel line to only have "rescue"
|
||
|
+ at the end. As an example only:
|
||
|
+
|
||
|
+ [qubes-rescue]
|
||
|
+ options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx
|
||
|
+ kernel=vmlinuz-4.14.13-2.pvops.qubes.x86_64 rescue
|
||
|
+ ramdisk=initramfs-4.14.13-2.pvops.qubes.x86_64.img
|
||
|
+
|
||
|
+ Do not simply copy this section above. You will need to make sure
|
||
|
+ all of your existing parameters, vmlinuz and initramfs versions match
|
||
|
+ your current kernel(s) you are booting.
|
||
|
+
|
||
|
+3. At the top of the file, edit the default link to use your new entry.
|
||
|
+
|
||
|
+ [global]
|
||
|
+ default=qubes-rescue
|
||
|
+
|
||
|
+Now when you reboot using Boot Camp and select "Qubes OS", it will boot
|
||
|
+into Rescue mode.
|
||
|
+
|
||
|
+To revert, change the default entry back to the original first entry name."""
|
||
|
+ readme = "{}/{}".format(iutil.getSysroot() + self._mountpoint, "00-README.txt")
|
||
|
+ try:
|
||
|
+ with open(readme, "w") as readme_file:
|
||
|
+ readme_file.write(text)
|
||
|
+ except IOError as error:
|
||
|
+ log.error("mactel failed to open %s for write: %s", readme, error)
|
||
|
+
|
||
|
+ def mactel_install_qubes_artwork(self):
|
||
|
+ """Configures the Qubes logo and label for macOS boot selector.
|
||
|
+
|
||
|
+ Shows during boot selection options.
|
||
|
+
|
||
|
+ .disk_label is defined as a text representation of the volume
|
||
|
+ label converted to a special image. See this link for
|
||
|
+ more details: http://refit.sourceforge.net/info/vollabel.html
|
||
|
+
|
||
|
+ .VolumeIcon.icns is defined as an ICNS image format of 512x512.
|
||
|
+ """
|
||
|
+
|
||
|
+ log.info("mactel creating Qubes Artwork")
|
||
|
+
|
||
|
+ artwork_dir = self._mactel_artwork_dir
|
||
|
+ if not os.path.exists(artwork_dir):
|
||
|
+ log.debug("mactel using sysroot for artwork prefix")
|
||
|
+ artwork_dir = iutil.getSysroot() + artwork_dir
|
||
|
+ if not os.path.exists(artwork_dir):
|
||
|
+ log.warning("mactel artwork missing from: %s", artwork_dir)
|
||
|
+ return
|
||
|
+
|
||
|
+ icon_src = artwork_dir + ".icns"
|
||
|
+ if os.path.exists(icon_src):
|
||
|
+ icon_dst_fil = "{}/{}".format(iutil.getSysroot() + self._mountpoint, ".VolumeIcon.icns")
|
||
|
+ self._copy_file(icon_src, icon_dst_fil)
|
||
|
+ else:
|
||
|
+ log.warning("mactel volume icon not found: %s", icon_src)
|
||
|
+
|
||
|
+ src_files = os.listdir(artwork_dir)
|
||
|
+ for file_name in src_files:
|
||
|
+ full_file_name = "{}/{}".format(artwork_dir, file_name)
|
||
|
+ if os.path.isfile(full_file_name):
|
||
|
+ sys_dir_file_name = "{}/{}".format(iutil.getSysroot() + self._mactel_sys_dir, "." + file_name)
|
||
|
+ self._copy_file(full_file_name, sys_dir_file_name)
|
||
|
+ config_dir = iutil.getSysroot() + self.config_dir
|
||
|
+ if os.path.exists(config_dir):
|
||
|
+ dest = "{}/{}".format(config_dir, "." + file_name)
|
||
|
+ self._copy_file(full_file_name, dest)
|
||
|
+
|
||
|
def install(self, args=None):
|
||
|
super(MacEFIGRUB, self).install()
|
||
|
+ log.info("Installing mactel MacEFI")
|
||
|
self.mactel_config()
|
||
|
+ self.mactel_install_qubes_artwork()
|
||
|
|
||
|
def is_valid_stage1_device(self, device, early=False):
|
||
|
valid = super(MacEFIGRUB, self).is_valid_stage1_device(device, early)
|
||
|
@@ -1932,6 +2124,24 @@ class MacEFIGRUB(EFIGRUB):
|
||
|
log.debug("MacEFIGRUB.is_valid_stage1_device(%s) returning %s", device.name, valid)
|
||
|
return valid
|
||
|
|
||
|
+ @staticmethod
|
||
|
+ def _symlink(source="", target=""):
|
||
|
+ """Creates a symlink between source and target."""
|
||
|
+ try:
|
||
|
+ os.symlink(source, target)
|
||
|
+ except OSError as error:
|
||
|
+ log.error("mactel failed to symlink %s -> %s : %s", source, target, error)
|
||
|
+
|
||
|
+ @staticmethod
|
||
|
+ def _copy_file(source="", target=""):
|
||
|
+ """Copies source to target using shutil.
|
||
|
+
|
||
|
+ Also chmod to 0644."""
|
||
|
+ try:
|
||
|
+ shutil.copy(source, target)
|
||
|
+ os.chmod(target, 0o644)
|
||
|
+ except OSError as error:
|
||
|
+ log.error("mactel failed to copy %s to %s: %s", source, target, error)
|
||
|
|
||
|
# Inherit abstract methods from BootLoader
|
||
|
# pylint: disable=abstract-method
|
||
|
--
|
||
|
2.14.4
|
||
|
|