Add new daemon for update handling

Currently developed to handle bug in macOS Sonoma that fails to clear problamatic kernel extensions in /Library/Extensions which could result in update failures from 14.0 to 14.1
This commit is contained in:
Mykola Grymalyuk
2023-10-25 21:33:34 -06:00
parent 2ff132cb04
commit ba0a676ca0
10 changed files with 131 additions and 37 deletions

View File

@@ -241,6 +241,7 @@ class CreateBinary:
whitelist_files = [ whitelist_files = [
"com.dortania.opencore-legacy-patcher.auto-patch.plist", "com.dortania.opencore-legacy-patcher.auto-patch.plist",
"com.dortania.opencore-legacy-patcher.rsr-monitor.plist", "com.dortania.opencore-legacy-patcher.rsr-monitor.plist",
"com.dortania.opencore-legacy-patcher.macos-update.plist",
"entitlements.plist", "entitlements.plist",
"launcher.sh", "launcher.sh",
"OC-Patcher-TUI.icns", "OC-Patcher-TUI.icns",

View File

@@ -5,6 +5,12 @@
- Add support for detecting T1 Security Chips in DFU mode - Add support for detecting T1 Security Chips in DFU mode
- Update non-Metal Binaries for macOS Sonoma: - Update non-Metal Binaries for macOS Sonoma:
- Resolve Photos app crash - Resolve Photos app crash
- Add new Launch Daemon for clean up on macOS updates
- Resolves KDKless Macs failing to boot after updating from 14.0 to 14.x
- `/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.macos-update.plist`
- Remove News Widget removal from Control Centre
- News Widget no longer crashes on 3802-based GPUs
- Resolve i210 NIC support for macOS Sonoma
- Increment Binaries: - Increment Binaries:
- PatcherSupportPkg 1.4.3 - release - PatcherSupportPkg 1.4.3 - release

View File

@@ -0,0 +1,19 @@
<?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>AssociatedBundleIdentifiers</key>
<string>com.dortania.opencore-legacy-patcher</string>
<key>Label</key>
<string>com.dortania.opencore-legacy-patcher.rsr-monitor</string>
<key>ProgramArguments</key>
<array>
<string>/Library/Application Support/Dortania/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher</string>
<string>--prepare_for_update</string>
</array>
<key>WatchPaths</key>
<array>
<string>/System/Volumes/Update/Update.plist</string>
</array>
</dict>
</plist>

View File

@@ -1,12 +1,16 @@
import threading import sys
import time import time
import logging import logging
import sys import plistlib
import threading
import subprocess
from resources import defaults, utilities, validation, constants from pathlib import Path
from resources.sys_patch import sys_patch, sys_patch_auto
from data import model_array, os_data
from resources.build import build from resources.build import build
from data import model_array from resources.sys_patch import sys_patch, sys_patch_auto
from resources import defaults, utilities, validation, constants
# Generic building args # Generic building args
@@ -41,6 +45,11 @@ class arguments:
self._sys_unpatch_handler() self._sys_unpatch_handler()
return return
if self.args.prepare_for_update:
logging.info("Preparing host for macOS update")
self._clean_le_handler()
return
if self.args.auto_patch: if self.args.auto_patch:
self._sys_patch_auto_handler() self._sys_patch_auto_handler()
return return
@@ -88,6 +97,46 @@ class arguments:
sys_patch_auto.AutomaticSysPatch(self.constants).start_auto_patch() sys_patch_auto.AutomaticSysPatch(self.constants).start_auto_patch()
def _clean_le_handler(self) -> None:
"""
Check if software update is staged
If so, clean /Library/Extensions
"""
if self.constants.detected_os < os_data.os_data.sonoma:
logging.info("Host doesn't require cleaning, skipping")
return
update_config = "/System/Volumes/Update/Update.plist"
if not Path(update_config).exists():
logging.info("No update staged, skipping")
return
try:
update_staged = plistlib.load(open(update_config, "rb"))
except Exception as e:
logging.error(f"Failed to load update config: {e}")
return
if "update-asset-attributes" not in update_staged:
logging.info("No update staged, skipping")
return
logging.info("Update staged, cleaning /Library/Extensions")
for kext in Path("/Library/Extensions").glob("*.kext"):
if not Path(f"{kext}/Contents/Info.plist").exists():
continue
try:
kext_plist = plistlib.load(open(f"{kext}/Contents/Info.plist", "rb"))
except Exception as e:
logging.info(f" - Failed to load plist for {kext.name}: {e}")
continue
if "GPUCompanionBundles" not in kext_plist:
continue
logging.info(f" - Removing {kext.name}")
subprocess.run(["rm", "-rf", kext])
def _build_handler(self) -> None: def _build_handler(self) -> None:
""" """
Start config building process Start config building process

View File

@@ -35,6 +35,7 @@ class BuildWiredNetworking:
# Always enable due to chance of hot-plugging # Always enable due to chance of hot-plugging
self._usb_ecm_dongles() self._usb_ecm_dongles()
self._i210_handling()
def _usb_ecm_dongles(self) -> None: def _usb_ecm_dongles(self) -> None:
@@ -48,6 +49,21 @@ class BuildWiredNetworking:
# - Kext: AppleUSBECM.kext # - Kext: AppleUSBECM.kext
support.BuildSupport(self.model, self.constants, self.config).enable_kext("ECM-Override.kext", self.constants.ecm_override_version, self.constants.ecm_override_path) support.BuildSupport(self.model, self.constants, self.config).enable_kext("ECM-Override.kext", self.constants.ecm_override_version, self.constants.ecm_override_path)
def _i210_handling(self) -> None:
"""
PCIe i210 NIC Handling
"""
# i210 NICs are broke in macOS 14 due to driver kit downgrades
# See ECM logic for why it's always enabled
if not self.model in smbios_data.smbios_dictionary:
return
support.BuildSupport(self.model, self.constants, self.config).enable_kext("CatalinaIntelI210Ethernet.kext", self.constants.i210_version, self.constants.i210_path)
# Ivy Bridge and newer natively support DriverKit, so set MinKernel to 23.0.0
if smbios_data.smbios_dictionary[self.model]["CPU Generation"] >= cpu_data.CPUGen.ivy_bridge.value:
support.BuildSupport(self.model, self.constants, self.config).get_kext_by_bundle_path("CatalinaIntelI210Ethernet.kext")["MinKernel"] = "23.0.0"
def _on_model(self) -> None: def _on_model(self) -> None:
""" """
On-Model Hardware Detection Handling On-Model Hardware Detection Handling

View File

@@ -288,6 +288,10 @@ class Constants:
def rsr_monitor_launch_daemon_path(self): def rsr_monitor_launch_daemon_path(self):
return self.payload_path / Path("com.dortania.opencore-legacy-patcher.rsr-monitor.plist") return self.payload_path / Path("com.dortania.opencore-legacy-patcher.rsr-monitor.plist")
@property
def update_launch_daemon_path(self):
return self.payload_path / Path("com.dortania.opencore-legacy-patcher.macos-update.plist")
# ACPI # ACPI
@property @property
def pci_ssdt_path(self): def pci_ssdt_path(self):

View File

@@ -61,7 +61,7 @@ class InitializeLoggingSupport:
""" """
base_path = Path("~/Library/Logs").expanduser() base_path = Path("~/Library/Logs").expanduser()
if not base_path.exists(): if not base_path.exists() or str(base_path).startswith("/var/root/"):
# Likely in an installer environment, store in /Users/Shared # Likely in an installer environment, store in /Users/Shared
base_path = Path("/Users/Shared") base_path = Path("/Users/Shared")
else: else:
@@ -71,7 +71,7 @@ class InitializeLoggingSupport:
try: try:
base_path.mkdir() base_path.mkdir()
except Exception as e: except Exception as e:
logging.error(f"Failed to create Dortania folder: {e}") print(f"Failed to create Dortania folder: {e}")
base_path = Path("/Users/Shared") base_path = Path("/Users/Shared")
self.log_filepath = Path(f"{base_path}/{self.log_filename}").expanduser() self.log_filepath = Path(f"{base_path}/{self.log_filename}").expanduser()
@@ -194,6 +194,7 @@ class InitializeLoggingSupport:
logging.info('#' * str_len) logging.info('#' * str_len)
logging.info("Log file set:") logging.info("Log file set:")
logging.info(f" {self.log_filepath}")
# Display relative path to avoid disclosing user's username # Display relative path to avoid disclosing user's username
try: try:
path = self.log_filepath.relative_to(Path.home()) path = self.log_filepath.relative_to(Path.home())

View File

@@ -716,8 +716,6 @@ class PatchSysVolume:
utilities.process_status(subprocess.run(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)) utilities.process_status(subprocess.run(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True))
if any(x in required_patches for x in ["AMD Legacy GCN", "AMD Legacy Polaris", "AMD Legacy Vega"]): if any(x in required_patches for x in ["AMD Legacy GCN", "AMD Legacy Polaris", "AMD Legacy Vega"]):
sys_patch_helpers.SysPatchHelpers(self.constants).disable_window_server_caching() sys_patch_helpers.SysPatchHelpers(self.constants).disable_window_server_caching()
if any(x in required_patches for x in ["Intel Ivy Bridge", "Intel Haswell"]):
sys_patch_helpers.SysPatchHelpers(self.constants).remove_news_widgets()
if "Metal 3802 Common Extended" in required_patches: if "Metal 3802 Common Extended" in required_patches:
sys_patch_helpers.SysPatchHelpers(self.constants).patch_gpu_compiler_libraries(mount_point=self.mount_location) sys_patch_helpers.SysPatchHelpers(self.constants).patch_gpu_compiler_libraries(mount_point=self.mount_location)

View File

@@ -377,36 +377,28 @@ Please check the Github page for more information about this release."""
subprocess.run(["xattr", "-cr", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) subprocess.run(["xattr", "-cr", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# Copy over our launch agent
logging.info("- Copying auto-patch.plist Launch Agent to /Library/LaunchAgents/")
if Path("/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist").exists():
logging.info("- Deleting existing auto-patch.plist")
utilities.process_status(utilities.elevated(["rm", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
if not Path("/Library/LaunchAgents/").exists():
logging.info("- Creating /Library/LaunchAgents/")
utilities.process_status(utilities.elevated(["mkdir", "-p", "/Library/LaunchAgents/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["cp", self.constants.auto_patch_launch_agent_path, "/Library/LaunchAgents/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
# Set the permissions on the com.dortania.opencore-legacy-patcher.auto-patch.plist services = {
logging.info("- Setting permissions on auto-patch.plist") self.constants.auto_patch_launch_agent_path: "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist",
utilities.process_status(utilities.elevated(["chmod", "644", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) self.constants.update_launch_daemon_path: "/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.macos-update.plist",
utilities.process_status(utilities.elevated(["chown", "root:wheel", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) **({ self.constants.rsr_monitor_launch_daemon_path: "/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.rsr-monitor.plist" } if self._create_rsr_monitor_daemon() else {}),
}
# Copy over our launch daemon for service in services:
if self._create_rsr_monitor_daemon() is True: name = Path(service).name
logging.info("- Copying rsr-monitor.plist Launch Daemon to /Library/LaunchDaemons/") logging.info(f"- Installing {name}")
if Path("/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.rsr-monitor.plist").exists(): if Path(services[service]).exists():
logging.info("- Deleting existing rsr-monitor.plist") logging.info(f" - Existing service found, removing")
utilities.process_status(utilities.elevated(["rm", "/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.rsr-monitor.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) utilities.process_status(utilities.elevated(["rm", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
if not Path("/Library/LaunchDaemons/").exists(): # Create parent directories
logging.info("- Creating /Library/LaunchDaemons/") if not Path(services[service]).parent.exists():
utilities.process_status(utilities.elevated(["mkdir", "-p", "/Library/LaunchDaemons/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) logging.info(f" - Creating {Path(services[service]).parent} directory")
utilities.process_status(utilities.elevated(["cp", self.constants.rsr_monitor_launch_daemon_path, "/Library/LaunchDaemons/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) utilities.process_status(utilities.elevated(["mkdir", "-p", Path(services[service]).parent], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["cp", service, services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
# Set the permissions on the com.dortania.opencore-legacy-patcher.rsr-monitor.plist # Set the permissions on the service
logging.info("- Setting permissions on rsr-monitor.plist") utilities.process_status(utilities.elevated(["chmod", "644", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["chmod", "644", "/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.rsr-monitor.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) utilities.process_status(utilities.elevated(["chown", "root:wheel", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["chown", "root:wheel", "/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.rsr-monitor.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
# Making app alias # Making app alias
# Simply an easy way for users to notice the app # Simply an easy way for users to notice the app

View File

@@ -580,6 +580,7 @@ def check_cli_args():
# sys_patch args # sys_patch args
parser.add_argument("--patch_sys_vol", help="Patches root volume", action="store_true", required=False) parser.add_argument("--patch_sys_vol", help="Patches root volume", action="store_true", required=False)
parser.add_argument("--unpatch_sys_vol", help="Unpatches root volume, EXPERIMENTAL", action="store_true", required=False) parser.add_argument("--unpatch_sys_vol", help="Unpatches root volume, EXPERIMENTAL", action="store_true", required=False)
parser.add_argument("--prepare_for_update", help="Prepares host for macOS update, ex. clean /Library/Extensions", action="store_true", required=False)
# validation args # validation args
parser.add_argument("--validate", help="Runs Validation Tests for CI", action="store_true", required=False) parser.add_argument("--validate", help="Runs Validation Tests for CI", action="store_true", required=False)
@@ -591,7 +592,14 @@ def check_cli_args():
parser.add_argument("--update_installed", help="Prompt user to finish updating via GUI", action="store_true", required=False) parser.add_argument("--update_installed", help="Prompt user to finish updating via GUI", action="store_true", required=False)
args = parser.parse_args() args = parser.parse_args()
if not (args.build or args.patch_sys_vol or args.unpatch_sys_vol or args.validate or args.auto_patch): if not (
args.build or
args.patch_sys_vol or
args.unpatch_sys_vol or
args.validate or
args.auto_patch or
args.prepare_for_update
):
return None return None
else: else:
return args return args