Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 111 additions & 12 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from dataclasses import dataclass

import system, osenum, stub, diskutil, osinstall, asahi_firmware, m1n1, bugs
from urlcache import NetworkError
from util import *

PART_ALIGN = psize("1MiB")
Expand Down Expand Up @@ -263,11 +264,25 @@ def action_install_into_container(self, avail_parts):
logging.info(f"Chosen IPSW version: {ipsw.version}")

self.ins = stub.StubInstaller(self.sysinfo, self.dutil, self.osinfo)
self.ins.load_ipsw(ipsw)
self.osins = osinstall.OSInstaller(self.dutil, self.data, template)
self.osins.load_package()
while True:
try:
self.ins.load_ipsw(ipsw)
self.osins.load_package()
break
except NetworkError as e:
logging.error(f"Network error: {e}")
print()
p_error(f"Download failed: {e}")
p_message("Please check your internet connection.")
if not self.yesno("Retry"):
return True
print()

self.do_install()
try:
self.do_install()
except NetworkError as e:
self._handle_post_partition_network_error(e)

def action_wipe(self):
p_warning("This will wipe all data on the currently selected disk.")
Expand All @@ -280,27 +295,63 @@ def action_wipe(self):
template = self.choose_os()

self.osins = osinstall.OSInstaller(self.dutil, self.data, template)
self.osins.load_package()
while True:
try:
self.osins.load_package()
break
except NetworkError as e:
logging.error(f"Network error: {e}")
print()
p_error(f"Download failed: {e}")
p_message("Please check your internet connection.")
if not self.yesno("Retry"):
return True
print()

min_size = STUB_SIZE + (self.osins.min_size if self.expert else self.osins.min_recommended_size)
print()
p_message(f"Minimum required space for this OS: {ssize(min_size)}")

start, end = self.dutil.get_disk_usable_range(self.cur_disk)
os_size = self.get_os_size_and_info(end - start, min_size, template)
while True:
try:
os_size = self.get_os_size_and_info(end - start, min_size, template)
break
except NetworkError as e:
logging.error(f"Network error: {e}")
print()
p_error(f"Download failed: {e}")
p_message("Please check your internet connection.")
if not self.yesno("Retry"):
return True
print()

p_progress(f"Partitioning the whole disk ({self.cur_disk})")
self.part = self.dutil.partitionDisk(self.cur_disk, "apfs", self.osins.name, STUB_SIZE)

p_progress(f"Creating new stub macOS named {self.osins.name}")
logging.info(f"Creating stub macOS: {self.osins.name}")
self.do_install(os_size)
try:
self.do_install(os_size)
except NetworkError as e:
self._handle_post_partition_network_error(e)

def action_install_into_free(self, avail_free):
template = self.choose_os()

self.osins = osinstall.OSInstaller(self.dutil, self.data, template)
self.osins.load_package()
while True:
try:
self.osins.load_package()
break
except NetworkError as e:
logging.error(f"Network error: {e}")
print()
p_error(f"Download failed: {e}")
p_message("Please check your internet connection.")
if not self.yesno("Retry"):
return True
print()

min_size = STUB_SIZE + (self.osins.min_size if self.expert else self.osins.min_recommended_size)
print()
Expand All @@ -327,13 +378,27 @@ def action_install_into_free(self, avail_free):
print()
p_message(f"Available free space: {ssize(free_part.size)}")

os_size = self.get_os_size_and_info(free_part.size, min_size, template)
while True:
try:
os_size = self.get_os_size_and_info(free_part.size, min_size, template)
break
except NetworkError as e:
logging.error(f"Network error: {e}")
print()
p_error(f"Download failed: {e}")
p_message("Please check your internet connection.")
if not self.yesno("Retry"):
return True
print()

p_progress(f"Creating new stub macOS named {self.osins.name}")
logging.info(f"Creating stub macOS: {self.osins.name}")
self.part = self.dutil.addPartition(free_part.name, "apfs", self.osins.name, STUB_SIZE)

self.do_install(os_size)
try:
self.do_install(os_size)
except NetworkError as e:
self._handle_post_partition_network_error(e)

def get_os_size_and_info(self, free_size, min_size, template):
os_size = None
Expand Down Expand Up @@ -488,9 +553,19 @@ def action_rebuild_vendorfw(self, oses):
p_message("Unable to rebuild firmware")
return False

self.ins.load_ipsw(ipsw)
self.ins.load_identity()
self.ins.collect_firmware(fw_pkg)
try:
self.ins.load_ipsw(ipsw)
self.ins.load_identity()
self.ins.collect_firmware(fw_pkg)
except NetworkError as e:
logging.error(f"Network error during firmware rebuild: {e}")
print()
p_error(f"Firmware rebuild failed: {e}")
p_message("Please check your internet connection and try again.")
print()
p_message("Press enter to return to the main menu.")
self.input()
return True
fw_pkg.close()

p_plain(f" Copying firmware into {target.name} partition...")
Expand All @@ -505,6 +580,23 @@ def action_rebuild_vendorfw(self, oses):

return True

def _handle_post_partition_network_error(self, e):
logging.error(f"Network error during installation: {e}")
print()
p_error("Installation failed due to a network error.")
p_error(f" {e}")
print()
p_message("Please check your internet connection, then re-run the installer.")
p_message("The installer will detect the incomplete installation and offer")
p_message("to repair it.")
print()
p_warning("If you need to file a bug report, please attach the log file:")
p_warning(f" {os.getcwd()}/installer.log")
print()
p_message("Press enter to exit.")
self.input()
sys.exit(1)

def do_install(self, total_size=None):
p_progress(f"Installing stub macOS into {self.part.name} ({self.part.label})")

Expand Down Expand Up @@ -1151,6 +1243,13 @@ def main_loop(self):
print()
logging.info("KeyboardInterrupt")
p_error("Interrupted")
except NetworkError as e:
logging.exception("Network error")
p_error(f"Installation failed due to a network error: {e}")
print()
p_message("Please check your internet connection and try again.")
p_warning("If you need to file a bug report, please attach the log file:")
p_warning(f" {os.getcwd()}/installer.log")
except subprocess.CalledProcessError as e:
cmd = shlex.join(e.cmd)
p_error(f"Failed to run process: {cmd}")
Expand Down
47 changes: 32 additions & 15 deletions src/urlcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
from dataclasses import dataclass

from urllib import parse
from http.client import HTTPSConnection, HTTPConnection
from http.client import HTTPSConnection, HTTPConnection, HTTPException
from util import *

class NetworkError(Exception):
pass

@dataclass
class CacheBlock:
idx: int
Expand Down Expand Up @@ -78,19 +81,31 @@ def seekable(self):
return True

def get_size(self):
for i in range(10):
con = self.get_con()
con.request("HEAD", self.url.path, headers={"Connection":" keep-alive"})
res = con.getresponse()
res.read()
loc = res.getheader("Location", None)
if loc is not None:
self.url = parse.urlparse(loc)
self.con = None
continue
return int(res.getheader("Content-length"))

raise Exception("Maximum number of redirects reached")
retries = 5
sleep = 1
for retry in range(retries + 1):
try:
for i in range(10):
con = self.get_con()
con.request("HEAD", self.url.path, headers={"Connection":" keep-alive"})
res = con.getresponse()
res.read()
loc = res.getheader("Location", None)
if loc is not None:
self.url = parse.urlparse(loc)
self.con = None
continue
return int(res.getheader("Content-length"))
raise Exception("Maximum number of redirects reached")
except (OSError, HTTPException) as e:
if retry == retries:
raise NetworkError(
f"Failed to connect to {self.url.netloc} after multiple retries"
) from e
p_warning(f"Connection error ({e}), retrying... ({retry + 1}/{retries})")
time.sleep(sleep)
self.close_connection()
sleep += 1

def get_partial(self, off, size, bypass_cache=False):
path = self.url.path
Expand Down Expand Up @@ -147,7 +162,9 @@ def get_block(self, blk, readahead=1):
except Exception as e:
if retry == retries:
p_error(f"Exceeded maximum retries downloading data.")
raise
raise NetworkError(
f"Download failed: lost connection to {self.url.netloc}"
) from e
p_warning(f"Error downloading data ({e}), retrying... ({retry + 1}/{retries})")
time.sleep(sleep)
self.close_connection()
Expand Down